Back to Design Patterns
Structural Pattern

Bridge Pattern

Decouple an abstraction from its implementation so that the two can vary independently — avoiding a permanent binding between them.

Intermediate10 min read

What is the Bridge Pattern?

The Bridge pattern separates a class into two independent hierarchies — the abstraction (what it does) and the implementation (how it does it) — connected by a reference (the bridge). Both can evolve independently without a combinatorial explosion of subclasses.

Imagine a notification system: you have notification types (Alert, Reminder, Welcome) and delivery channels (Email, SMS, Slack). Without Bridge, you'd need AlertEmail, AlertSMS, AlertSlack, ReminderEmail, ReminderSMS... 3×3 = 9 classes. Add one type or channel and it doubles. With Bridge: 3 + 3 = 6 classes total, any combination.

The key insight: use composition instead of inheritance to connect the two hierarchies. The abstraction holds a reference to an implementor and delegates platform-specific work to it.

Key Idea: Split one large class hierarchy into two smaller ones — abstraction and implementation — and connect them with a reference. Change either side independently.

The Cartesian Product Problem

Without Bridge, adding dimensions to a class hierarchy causes class count to multiply:

// Without Bridge — 3 types × 3 channels = 9 classes
class AlertEmailNotification    {}
class AlertSMSNotification      {}
class AlertSlackNotification    {}
class ReminderEmailNotification {}
class ReminderSMSNotification   {}
class ReminderSlackNotification {}
class WelcomeEmailNotification  {}
class WelcomeSMSNotification    {}
class WelcomeSlackNotification  {}
// Add 1 new type → 3 more classes. Add 1 new channel → 3 more classes.

// With Bridge — 3 types + 3 senders = 6 classes, infinite combos
class AlertNotification    { constructor(sender) {} }
class ReminderNotification { constructor(sender) {} }
class WelcomeNotification  { constructor(sender) {} }
class EmailSender  {}
class SMSSender    {}
class SlackSender  {}
// new AlertNotification(new SMSSender()) — zero new classes!

Rule of thumb: If you see a class hierarchy growing in two independent dimensions simultaneously, Bridge is likely the solution.

Core Participants

Abstraction

Abstraction

The high-level control layer. Defines the interface the client uses. Holds a reference to an Implementor (the bridge). Delegates platform-specific work to the implementor.

Refined Abstraction

Refined Abstraction

Extends the Abstraction with more specific behaviour. Still delegates to the implementor — doesn't care which concrete implementor it holds.

Implementor

Implementor Interface

Defines the interface for the implementation layer. Typically lower-level operations that the Abstraction composes into higher-level operations.

Concrete Implementor

Concrete Implementor

Platform-specific implementations of the Implementor interface — the actual "how". Each provides a different backend, channel, or technology.

Implementation

// Bridge Pattern — Notification System

// ─── Implementor interface ────────────────────────────────────
interface MessageSender {
    void send(String recipient, String subject, String body);
}

// ─── Concrete Implementors ────────────────────────────────────
class EmailSender implements MessageSender {
    public void send(String recipient, String subject, String body) {
        System.out.println("[Email] To: " + recipient + " | Subject: " + subject + " | " + body);
    }
}

class SMSSender implements MessageSender {
    public void send(String recipient, String subject, String body) {
        // SMS has no subject, truncate body
        System.out.println("[SMS] To: " + recipient + " | " + body.substring(0, Math.min(160, body.length())));
    }
}

class SlackSender implements MessageSender {
    public void send(String recipient, String subject, String body) {
        System.out.println("[Slack] Channel: #" + recipient + " | *" + subject + "* " + body);
    }
}

// ─── Abstraction ──────────────────────────────────────────────
abstract class Notification {
    protected MessageSender sender;  // the bridge

    public Notification(MessageSender sender) { this.sender = sender; }

    public abstract void notify(String recipient, String message);
}

// ─── Refined Abstractions ─────────────────────────────────────
class AlertNotification extends Notification {
    public AlertNotification(MessageSender sender) { super(sender); }

    public void notify(String recipient, String message) {
        sender.send(recipient, "🚨 ALERT", "[URGENT] " + message);
    }
}

class ReminderNotification extends Notification {
    public ReminderNotification(MessageSender sender) { super(sender); }

    public void notify(String recipient, String message) {
        sender.send(recipient, "⏰ Reminder", message);
    }
}

class WelcomeNotification extends Notification {
    public WelcomeNotification(MessageSender sender) { super(sender); }

    public void notify(String recipient, String message) {
        sender.send(recipient, "👋 Welcome!", "Hi " + recipient + "! " + message);
    }
}

// ─── Client ───────────────────────────────────────────────────
public class App {
    public static void main(String[] args) {
        // Mix and match abstraction × implementation freely
        Notification emailAlert   = new AlertNotification(new EmailSender());
        Notification smsAlert     = new AlertNotification(new SMSSender());
        Notification slackRemind  = new ReminderNotification(new SlackSender());
        Notification emailWelcome = new WelcomeNotification(new EmailSender());

        emailAlert.notify("admin@example.com", "Server CPU at 98%!");
        smsAlert.notify("+91-9876543210",       "Server CPU at 98%!");
        slackRemind.notify("devops",            "Deployment at 3pm today");
        emailWelcome.notify("alice",            "Your account is ready.");
        // 3 types × 3 senders = 9 combos — zero new classes needed!
    }
}

When to Use Bridge

Two Independent Dimensions of Variation

When a class hierarchy grows in two directions at once — shape+renderer, notification+channel, exporter+format — Bridge prevents the cartesian explosion.

Shape+Renderer, Notification+Channel

Switch Implementations at Runtime

When you need to swap the underlying implementation at runtime without changing the abstraction — e.g., switch from local to remote storage without changing the business layer.

StorageBridge, PaymentBridge

Cross-Platform Development

When the same abstraction needs multiple platform implementations — the abstraction stays the same, concrete implementors handle platform specifics.

DrawingAPI, DatabaseDriver, UIToolkit

Pros & Cons

Advantages

  • Eliminates cartesian product class explosion
  • Abstraction and implementation vary independently
  • Switch implementation at runtime
  • Single Responsibility — separate abstraction from platform concerns
  • Open/Closed — add new abstractions or implementors without changing either side

Disadvantages

  • More complex upfront design
  • Overkill when only one implementation exists
  • Indirection makes code harder to follow
  • Requires good abstraction design — hard to retrofit

Real-World Examples

Java JDBC

JDBC is a textbook Bridge: Connection, Statement, ResultSet are the abstraction. MySQL driver, PostgreSQL driver, Oracle driver are the concrete implementors. Your code uses JDBC API regardless of the database.

SLF4J Logging (Java)

SLF4J is the abstraction (Logger interface). Logback, Log4j, and java.util.logging are the concrete implementors. Switch the logging backend by swapping the JAR — application code unchanged.

Python's logging Handlers

Python's logging.Logger is the abstraction. StreamHandler, FileHandler, SMTPHandler, SocketHandler are implementors. You can add multiple handlers to a single logger at runtime.

React's Reconciler

React's rendering pipeline is a Bridge: the reconciliation algorithm (abstraction) is separate from the renderer (implementor). React DOM, React Native, and React Three Fiber are all different implementors of the same reconciler.

What's Next?

Explore related patterns: