The idea
What it is
Bridge is a structural pattern with a one-sentence intent: split a class into two separate hierarchies — an abstraction and an implementation — and connect them with composition, so both sides can vary independently. The word 'bridge' is the object reference that links the two sides: the abstraction has a implementation and delegates the real work to it.
Picture remote controls and devices. A remote (Basic or Advanced) is what the user holds — the high-level control. A device (TV, Radio, Speaker) is what actually does the work — the low-level machinery. Without Bridge, you'd be tempted to make a subclass for every pairing: BasicTVRemote, AdvancedTVRemote, BasicRadioRemote... 2 remotes × 3 devices = 6 classes, and adding one new device forces you to write 2 more. With Bridge, a Remote simply holds a Device field. Two small hierarchies — 2 + 3 = 5 classes — and any remote drives any device.
The one sentence to remember
When a class wants to vary in two independent directions at once, don't multiply subclasses — split it into an abstraction hierarchy and an implementation hierarchy, and let the abstraction hold an implementation. Growth becomes m + n classes instead of m × n.
'Abstraction' here does not mean 'abstract class'
In Bridge, abstraction just means the high-level control side (the remote — what callers talk to), and implementation means the low-level work side (the device — what actually does it). Either side may or may not use abstract classes in code. Don't confuse this with [[abstraction]] the OOP pillar — same word, narrower meaning here.
Mechanics
How it works
The four participants
Bridge has four roles, two per side. The bridge itself is just one field: the abstraction holds a reference to an implementor.
- Abstraction (
Remote) — the high-level control that callers use. It holds adevicefield and delegates real work to it:volumeUp()callsdevice.setVolume(...). - Refined Abstraction (
AdvancedRemote) — an optional richer subclass of the abstraction. It adds higher-level features likemute()— still built out of implementor calls. - Implementor interface (
Device) — the contract the abstraction talks through:isEnabled(),setVolume(v),getVolume(). This interface is the bridge. - Concrete Implementors (
Tv,Radio,Speaker) — the real platform-specific work. Each implementsDevicein its own way.
The m × n math
This is why the pattern exists. With one merged hierarchy, every combination is a class: m remote kinds × n device kinds = m × n classes. 2×3 = 6. Add a Projector: 8. Add a Kids remote too: 12. Every new idea on either side multiplies. With Bridge you keep two separate hierarchies: m + n classes. 2+3 = 5. Add a Projector: 6. Add a Kids remote: 7. Every new idea on either side adds exactly one class — and it instantly works with everything on the other side.
interface Device { // the Implementor — this IS the bridge
setVolume(v: number): void;
getVolume(): number;
}
class Remote { // the Abstraction
constructor(protected device: Device) {} // 🌉 has-a Device (composition)
volumeUp(): void {
this.device.setVolume(this.device.getVolume() + 10); // delegate
}
}
class AdvancedRemote extends Remote { // Refined Abstraction
mute(): void { this.device.setVolume(0); }
}
// Any remote drives any device — pick the pair at runtime:
const remote = new AdvancedRemote(new Tv()); // or new Radio(), new Speaker()
remote.volumeUp();Notice what Remote doesn't know: whether it's driving a TV or a radio. It only knows the Device interface. That's the bridge doing its job — the remote side and the device side only meet at one thin interface, so each can grow, change, or be swapped without touching the other.
It's composition over inheritance — applied to two dimensions
Bridge is exactly the [[composition-vs-inheritance]] lesson put to work. Inheritance would weld 'what kind of remote' and 'what kind of device' into one class name. Composition keeps them as two separate objects joined by a field — so the two dimensions vary independently.
Bridge vs Adapter — designed vs retrofitted
Bridge and [[adapter]] look similar on a diagram — both wrap a call through an interface. The difference is when and why. Adapter is a retrofit: two existing, incompatible interfaces already exist, and you glue them together after the fact. Bridge is planned up front: you design the abstraction and implementation as two hierarchies from the start, on purpose, so both can evolve independently. Adapter fixes a past mismatch; Bridge prevents a future explosion.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
Remote controls driving devices, in two worlds. In WITHOUT Bridge, every remote×device combo is its own class — six cards fill the grid, and pressing + Add Projector device makes two more appear (one per remote type), while + Add Kids remote makes three more (one per device). Watch the classes counter climb and the grid get crowded — that explosion is the whole problem. Flip to WITH Bridge and the same system is two tidy columns — REMOTES and DEVICES — joined by a has-a arrow. The same add buttons now add exactly one card each. Then use the demo strip: pick any remote, pick any device, press 🔊 volume +, and watch that device's volume bar rise — proof that any remote can drive any device across the bridge.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Watch the explosion in the WITHOUT world
Open the prototype with the toggle on WITHOUT Bridge. The grid shows six class cards — one per remote×device combo — and the chip reads classes: 6. Press '+ Add Projector device': two new red-flashing cards appear (BasicProjectorRemote, AdvancedProjectorRemote) and the counter jumps to 8. Press '+ Add Kids remote': three more cards, counter 11. One new idea on either side multiplies the grid — feel how crowded it gets.
Flip to WITH Bridge and add again
Switch the toggle to WITH Bridge. The same system is now two columns — REMOTES (Basic, Advanced) and DEVICES (TV, Radio, Speaker) — joined by a has-a arrow, and the chip reads classes: 5. Press the same two add buttons. Projector adds one card to the DEVICES column (5→6); Kids adds one card to REMOTES (6→7). Each new idea costs exactly one class, and it works with the whole other column immediately.
Drive any device with any remote
Still in the WITH world, use the demo strip at the bottom: pick a remote chip (Basic or Advanced, or Kids if you added it), pick a device chip (TV, Radio, Speaker...), then press '🔊 volume +'. The picked device's volume bar rises. Now keep the same remote and pick a different device — it works there too. No BasicTVRemote class exists anywhere; the remote just calls device.setVolume() across the bridge.
In practice
When to use it — and what trips people up
Reach for Bridge when two dimensions want to vary
- A class is growing in two independent directions — kind of control × kind of backend, shape × renderer, notification × delivery channel. Two hierarchies joined by a field beat one hierarchy of combos.
- Subclass names start containing an '×' —
BasicTVRemote,SqlWindowsLogger,CircleOpenGLRenderer. Compound names are the smell: each word is a dimension that should be its own hierarchy. - You want to swap the implementation at runtime — hand the same remote a different device mid-program. Inheritance fixes the pairing at compile time; a composed field can be reassigned.
- Platform or backend variations — one API surface over Windows/macOS/Linux implementations, or one storage interface over Postgres/SQLite/in-memory. Callers see the abstraction; the implementor hides the platform.
When NOT to use it
If only one dimension varies, you don't need Bridge — a plain interface with a few implementations is enough (that's just [[program-to-interfaces]]). Bridge earns its extra indirection only when both sides genuinely grow. Splitting a stable, single-dimension class into two hierarchies is complexity with no payoff.
What it gives you
- Kills the subclass explosion — m + n classes instead of m × n, and every new class on one side works with the entire other side for free.
- Both sides evolve independently — you can add a Projector without touching any remote, or a Kids remote without touching any device (Open/Closed in both dimensions).
- Implementations are swappable at runtime — the abstraction holds a reference, so you can hand the same remote a different device mid-program.
- Hides platform detail from callers — client code talks to the abstraction only, so platform-specific implementors can change or multiply behind the interface.
Common mistakes
- More moving parts up front — two hierarchies plus an interface is more code than one class, which feels heavy while the design is still small.
- The implementor interface is a commitment — it must be designed before the split pays off, and changing it later ripples through every implementor.
- Indirection costs readability — following volumeUp() means hopping from Remote to Device to Tv; on a highly cohesive class the split can hurt more than help.
- Easy to confuse with look-alike patterns — Adapter, Strategy, and State all 'delegate through an interface'; misnaming the intent misleads readers about why the split exists.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// Implementor — the low-level side. This interface IS the bridge.
interface Device {
setVolume(v: number): void;
getVolume(): number;
name(): string;
}
// Concrete Implementors — each device works its own way.
class Tv implements Device {
private vol = 30;
setVolume(v: number) { this.vol = Math.max(0, Math.min(100, v)); }
getVolume() { return this.vol; }
name() { return "TV"; }
}
class Radio implements Device {
private vol = 50;
setVolume(v: number) { this.vol = Math.max(0, Math.min(100, v)); }
getVolume() { return this.vol; }
name() { return "Radio"; }
}
// Abstraction — the high-level side. Holds a Device, delegates to it.
class Remote {
constructor(protected device: Device) {} // 🌉 has-a (composition)
volumeUp() { this.device.setVolume(this.device.getVolume() + 10); }
volumeDown() { this.device.setVolume(this.device.getVolume() - 10); }
}
// Refined Abstraction — richer remote, same bridge underneath.
class AdvancedRemote extends Remote {
mute() { this.device.setVolume(0); }
}
// Any remote drives any device — no combo classes anywhere.
const r1 = new Remote(new Tv());
const r2 = new AdvancedRemote(new Radio());
r1.volumeUp(); // TV: 30 → 40
r2.mute(); // Radio: 50 → 0
console.log(r2 instanceof Remote); // true — one remote hierarchyReferences & further reading
5 sources- Articlerefactoring.guru
Bridge — Refactoring.Guru
The clearest illustrated walkthrough, built on the same remote-control × device example: the class-explosion problem, the two hierarchies, and code in several languages. Best first read.
- Docsen.wikipedia.org
Bridge pattern — Wikipedia
Concise reference with the formal GoF intent, UML structure, and short examples across languages — good for a quick structural recap.
- Booken.wikipedia.org
Design Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, Vlissides
The original 'Gang of Four' book that catalogued Bridge. Its motivating example — windowing systems across platforms — is the classic two-dimension problem.
- Articlesourcemaking.com
Bridge Design Pattern — SourceMaking
Short treatment focused on intent and the 'decouple abstraction from implementation' checklist, with rules of thumb for spotting when the split is worth it.
- Articlebaeldung.com
Bridge Pattern in Java — Baeldung
A compact Java implementation walkthrough — handy for seeing the pattern in idiomatic Java and comparing it against Adapter and Strategy.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 05
What is the intent of the Bridge pattern?
question 02 / 05
You have 4 remote types and 5 device types. How many classes do you need without Bridge (one subclass per combo) versus with Bridge?
question 03 / 05
In the Bridge pattern, what exactly is 'the bridge'?
question 04 / 05
How is Bridge different from Adapter, given that both delegate through an interface?
question 05 / 05
When is Bridge the WRONG choice?
0/5 answered