Bridge Pattern
The Bridge pattern is a structural pattern for splitting a single class hierarchy that would otherwise grow in two independent dimensions into two separate hierarchies linked by composition. One hierarchy is the abstraction (what the client talks to), the other is the implementation (the platform, strategy, or low-level mechanism). The abstraction holds a reference to an implementor and delegates the work that varies across the second dimension.
The mechanism is simple: Car and Truck share a Vehicle parent, ManualTransmission and AutomaticTransmission share a Transmission interface, and the Vehicle base keeps a Transmission reference. New vehicle types do not require new transmission classes, and new transmission types do not require new vehicle classes.
The problem it solves
When a domain has two axes that can vary independently, naively modelling it with inheritance produces one class per combination. If there are M abstractions and N implementations, you get M × N concrete classes. Adding one new value on the abstraction axis forces N new subclasses (one per implementation); adding one on the implementation axis forces M — never just one.
Bridge fixes this by separating the axes. You still have M abstractions and N implementations, but only M + N classes total, plus one abstract base and one interface. Each new abstraction or implementation adds exactly one class.
Quantified example: shapes × renderers
Consider a UI library with shapes on one axis and rendering backends on another.
| Approach | Classes | Count |
|---|---|---|
| Without Bridge (inheritance) | CircleOpenGL, CircleDirectX, CircleSoftware, CircleVulkan, SquareOpenGL, SquareDirectX, SquareSoftware, SquareVulkan, TriangleOpenGL, TriangleDirectX, TriangleSoftware, TriangleVulkan | 3 × 4 = 12 |
| With Bridge | Shape (abstraction), Circle, Square, Triangle (refined abstractions), Renderer (implementor), OpenGLRenderer, DirectXRenderer, SoftwareRenderer, VulkanRenderer (concrete implementors) | 3 + 4 = 7 |
The saving grows with the axes: 5 shapes × 6 renderers without Bridge is 30 classes; with Bridge it is 11. The pattern pays off fastest when both M and N are at least two and expected to grow.
Boundary case: when Bridge does not pay off
If there is only one implementation (N = 1), Bridge adds one interface and one indirection for zero savings. For M abstractions and one implementation, inheritance needs M classes; Bridge needs M abstractions plus one implementor interface and one concrete implementor — strictly more code with no combinatorial win. Do not build a Bridge when the second axis is fixed.
Structure
- Abstraction — the high-level type the client uses. It defines the public interface and holds a reference to an
Implementor. In the example,Vehicleis the abstraction. - Refined Abstraction — concrete subclasses of the abstraction that add domain-specific behavior.
CarandTruckare refined abstractions. - Implementor — the interface for the low-level operations the abstraction delegates to.
Transmissionis the implementor. - Concrete Implementor — concrete classes that fulfill the implementor interface.
ManualTransmissionandAutomaticTransmissionare concrete implementors.
The decisive relationship is composition: Abstraction → Implementor. It must be chosen at construction (or injected later), and it is what makes the two hierarchies independent.
Worked example: Vehicle and Transmission
The example keeps the Vehicle/Transmission domain from the original lesson but assigns the roles correctly. Vehicle is the abstraction; Car and Truck are refined abstractions; Transmission is the implementor; ManualTransmission and AutomaticTransmission are concrete implementors. A vehicle is constructed with a transmission, and calls on the vehicle delegate to the transmission.
// Implementor
interface Transmission {
void applyGear();
}
// Concrete Implementors
class ManualTransmission implements Transmission {
@Override
public void applyGear() {
System.out.println("Manual transmission engaged.");
}
}
class AutomaticTransmission implements Transmission {
@Override
public void applyGear() {
System.out.println("Automatic transmission engaged.");
}
}
// Abstraction
abstract class Vehicle {
protected final Transmission transmission;
public Vehicle(Transmission transmission) {
this.transmission = transmission;
}
abstract void drive();
}
// Refined Abstractions
class Car extends Vehicle {
public Car(Transmission transmission) {
super(transmission);
}
@Override
void drive() {
System.out.print("Car: ");
transmission.applyGear();
}
}
class Truck extends Vehicle {
public Truck(Transmission transmission) {
super(transmission);
}
@Override
void drive() {
System.out.print("Truck: ");
transmission.applyGear();
}
}
// Client code
public class Solution {
public static void main(String[] args) {
Vehicle manualCar = new Car(new ManualTransmission());
Vehicle autoTruck = new Truck(new AutomaticTransmission());
manualCar.drive();
autoTruck.drive();
}
}
Output:
Car: Manual transmission engaged.
Truck: Automatic transmission engaged.
The same two hierarchies in Go:
package main
import "fmt"
// Implementor
type Transmission interface {
ApplyGear()
}
// Concrete Implementors
type ManualTransmission struct{}
func (m *ManualTransmission) ApplyGear() {
fmt.Println("Manual transmission engaged.")
}
type AutomaticTransmission struct{}
func (a *AutomaticTransmission) ApplyGear() {
fmt.Println("Automatic transmission engaged.")
}
// Abstraction
type Vehicle struct {
transmission Transmission
}
func (v *Vehicle) Drive() {
v.transmission.ApplyGear()
}
// Refined Abstractions
type Car struct {
Vehicle
}
func NewCar(t Transmission) *Car {
return &Car{Vehicle: Vehicle{transmission: t}}
}
func (c *Car) Drive() {
fmt.Print("Car: ")
c.Vehicle.Drive()
}
type Truck struct {
Vehicle
}
func NewTruck(t Transmission) *Truck {
return &Truck{Vehicle: Vehicle{transmission: t}}
}
func (t *Truck) Drive() {
fmt.Print("Truck: ")
t.Vehicle.Drive()
}
// Client code
func main() {
manualCar := NewCar(&ManualTransmission{})
autoTruck := NewTruck(&AutomaticTransmission{})
manualCar.Drive()
autoTruck.Drive()
}
Both versions compile and run as-is. The Java version uses an abstract class for the abstraction; the Go version uses struct embedding, since Go has no inheritance.
Bridge in the wild: JDBC and SLF4J
The Vehicle/Transmission toy makes the shape clear, but the reason to trust the pattern is that two of the most-used pieces of the Java ecosystem are Bridges, and they show the two axes growing independently for real, over decades.
JDBC. Your application codes against the java.sql abstraction — Connection, Statement, ResultSet. The implementor is the vendor driver: the PostgreSQL, MySQL, or Oracle driver each supplies the low-level wire protocol behind those same interfaces, and DriverManager is the wiring that binds an abstraction to a concrete implementor at connect time. This is the payoff stated concretely: your query code does not change when you swap postgresql.Driver for mysql.Driver (implementor axis grows — new database, one new driver), and driver vendors do not rewrite their drivers when the JDBC spec adds a method like Connection.setSchema (abstraction axis grows — one new operation, every driver keeps working). Neither side has ever had to coordinate a combinatorial matrix of "PostgreSQL-with-JDBC-4.2" classes — that is the M + N win in production.
SLF4J. The logging facade is a Bridge whose implementor is chosen at deploy time, not compile time. Application code calls LoggerFactory.getLogger(...) and the Logger interface (the abstraction); the concrete implementor — Logback, Log4j2, or java.util.logging — is whichever binding jar is on the classpath. Swap the jar and every log call in millions of lines re-targets a new backend, no source change. That "bind the implementor separately from the abstraction, and let each evolve on its own release cadence" is the defining Bridge move, and it is why SLF4J is the standard example of the pattern outside the textbook.
Both cases share the tell that separates a real Bridge from a mere Strategy swap: the abstraction side is itself a growing hierarchy of types the client relies on (the whole java.sql API surface; the logging levels, markers, and MDC of a logging API), not a single fixed context with one pluggable method.
Bridge versus alternatives
| Pattern | Structure | Intent | When to reach for it |
|---|---|---|---|
| Bridge | Two planned hierarchies joined by composition. | Let a high-level abstraction and its low-level implementation vary independently from the start. | You own both hierarchies, both are expected to grow, and the client wants stable high-level types. |
| Strategy | One context holds one interchangeable algorithm object. | Swap a single algorithm at runtime. | Only one dimension varies (the algorithm), not two orthogonal hierarchies. |
| Adapter | One wrapper translates one interface into another. | Make an existing class usable through a target interface you do not want to change. | You are retrofitting an existing, incompatible implementation you do not own or cannot redesign. |
Bridge vs Strategy
The code is structurally identical: an object holds a reference to an interface and delegates. The difference is intent and scope. Strategy swaps one algorithm inside one context — the context is fixed and the algorithm varies. Bridge is two planned hierarchies: the abstraction hierarchy is as open as the implementation hierarchy. If your "abstraction" has no subtypes of its own and will not acquire them, you have a Strategy, not a Bridge.
Bridge vs Adapter
Bridge is proactive: you design both sides together so they can evolve independently. Adapter is reactive: an implementation already exists with the wrong interface, and you wrap it to make it fit. A Bridge can contain an Adapter internally if it must integrate a legacy implementation, but the pattern itself is about planned separation, not retrofitting.
When to use — and when not
Use Bridge when:
- The abstraction and implementation can vary independently and both are expected to grow.
- You need a stable high-level API over multiple low-level mechanisms (renderers, platforms, protocols).
- The implementation count
N ≥ 2and the abstraction countM ≥ 2, or the testing seam justifies the indirection even when one axis is small.
Do not use Bridge when:
- Only one implementation exists or will ever exist (
N = 1). - Only one dimension varies — use Strategy instead.
- You are wrapping an existing incompatible class you cannot redesign — use Adapter instead.
- The added interface and indirection make the code harder to follow without a real combinatorial win.
Takeaways
- Bridge separates two independent axes of variation into two hierarchies joined by composition, turning an
M × Ninheritance explosion intoM + Nclasses. - The payoff depends on both axes growing. With only one implementation, Bridge adds cost without savings.
- Bridge, Strategy, and Adapter share the same delegation shape. Choose by intent: Bridge for two planned hierarchies, Strategy for one swappable algorithm, Adapter for retrofitting an existing class.
- Assign the roles precisely. The abstraction holds the implementor reference; refined abstractions add behavior; concrete implementors supply the low-level mechanism.
Sources: Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Gang of Four) — Bridge chapter; refactoring.guru — Bridge. Re-authored and deepened for the Knowledge Guide; the Vehicle/Transmission example is retained from the original extracted lesson, with corrected roles and added Go implementation.
Interview drills
Q1. Bridge vs Adapter?
Bridge is proactive: you design two orthogonal hierarchies up front so both can evolve independently. Adapter is reactive: an implementation already exists with the wrong interface and you wrap it to fit. A Bridge may contain an Adapter internally to absorb a legacy implementor, but its own intent is planned separation, not retrofitting.
Q2. Bridge vs Strategy?
The delegation shape is identical, so the distinction is intent and scope. Strategy swaps one algorithm behind a fixed context (only the algorithm axis varies). Bridge decouples an abstraction hierarchy that is as open as the implementation hierarchy — both axes grow. If your "abstraction" has no subtypes of its own and never will, you have a Strategy, not a Bridge.
Q3. When does plain M×N subclassing win?
When both M and N are tiny and frozen. The Bridge interface and indirection cost more than a handful of concrete classes, and with N=1 there is no combinatorial win at all — Bridge is then pure indirection tax.
🤖 Don't fully get this? Learn it with Claude
Stuck on Bridge Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Bridge Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Bridge Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Bridge Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Bridge Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Bridge Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.