The Bridge pattern is defined as 'decouple an abstraction from its implementation so that the two can vary independently.' What concrete problem does that solve, and how is Bridge different from Adapter and from Strategy?
answer
- two axes, M+N instead of M*N
- abstraction holds implementor = the bridge
- Adapter fixes afterwards, Bridge designs beforehand
- Strategy = one algorithm, Bridge = whole platform
- hardest part: choosing the primitive operations
basics
~20 sBridge splits one concept into two hierarchies — what it does and how it is done — connected by composition, so you can extend either side without multiplying classes. Adapter reconciles two interfaces that already exist and clash; Bridge is designed up front. Strategy swaps one algorithm, not a whole implementation hierarchy.
solid answer
~50 sBridge attacks combinatorial class explosion. If a shape hierarchy (circle, square) crosses a rendering hierarchy (vector, raster), inheritance forces M×N classes and every new member on either axis multiplies. Bridge makes the abstraction (Shape, holding a Renderer) and the implementor (Renderer, with vector/raster subclasses) separate hierarchies joined by composition: M+N classes, and each axis extends alone. The abstraction can also have refined subclasses that add higher-level operations built from primitive implementor calls. Versus Adapter: Adapter is retrofitted to make an existing incompatible component fit an interface you already call — the two sides were designed by different people at different times; Bridge is planned before either side exists, and both interfaces are yours. Versus Strategy: structurally almost identical (an object delegating to a pluggable collaborator), but Strategy's intent is interchangeable algorithms for one operation and is behavioral, while Bridge's implementor is a whole platform-level abstraction the entire class is built on.
code
pseudocode · 14 lines// Implementor hierarchy: HOW
interface Renderer { fun drawCircle(x, y, r); fun drawLine(x1, y1, x2, y2) }
class VectorRenderer : Renderer { /* emits SVG paths */ }
class RasterRenderer : Renderer { /* sets pixels */ }
// Abstraction hierarchy: WHAT (holds the bridge reference)
abstract class Shape(protected val r: Renderer) { abstract fun draw() }
class Circle(r: Renderer, val x, val y, val rad) : Shape(r) {
override fun draw() = r.drawCircle(x, y, rad)
}
class Square(r: Renderer, val x, val y, val side) : Shape(r) { /* four drawLine calls */ }
// 2 shapes x 2 renderers = 4 classes, not 4 combination subclasses;
// adding Triangle adds 1 class, adding PdfRenderer adds 1 class.go deeper
Say Bridge separates 'what' from 'how' into two hierarchies linked by composition, so you get M+N classes instead of M×N, and give the shape/renderer example.
Add the four roles, show how each axis extends independently, and contrast Adapter (retrofit for incompatibility) with Bridge (planned separation).
Discuss choosing the implementor primitives, leaky platform capabilities, when Bridge is premature, the runtime-swap and testability benefits, and the Strategy overlap by intent rather than structure.
Connect to hexagonal/ports-and-adapters and platform strategy: which axis the organisation actually varies, the cost of freezing a lowest-common-denominator interface across teams, and when to abandon independence and accept platform-specific paths.
## The problem Bridge exists for Start with one concept that varies along two independent axes. Classic examples: - **Shape** × **rendering technology** (vector, raster, OpenGL, SVG). - **Message** (alert, reminder, invoice) × **transport** (email, SMS, push, webhook). - **UI window** (plain, icon, transient) × **windowing system** (X11, Windows, macOS). - **Repository/DAO** (customer, order, invoice) × **storage** (SQL, document store, in-memory, file). - **Remote control** (basic, advanced) × **device** (TV, radio, speaker). Model that with inheritance alone and you get `VectorCircle`, `RasterCircle`, `VectorSquare`, `RasterSquare`… — the product M×N. Adding one shape adds N classes; adding one renderer adds M. Worse, the code duplicated across the row "all circles" and the column "all raster things" cannot be shared, because a class has only one place in the hierarchy. ## The Bridge structure Four roles: - **Abstraction** — the client-facing type (`Shape`), holding a reference to an Implementor. Defines high-level operations in terms of implementor primitives. - **Refined Abstraction** — subclasses of the abstraction adding or specialising high-level behaviour (`Circle`, `Square`). - **Implementor** — a *separate* interface of primitive operations (`Renderer.drawCircle(x, y, r)`, `Renderer.drawLine(...)`). Deliberately not the same interface as Abstraction; it is lower-level. - **Concrete Implementor** — `VectorRenderer`, `RasterRenderer`. The reference from Abstraction to Implementor is the "bridge". Class count drops from M×N to M+N, and the two hierarchies extend independently. The implementor can even be swapped at runtime if the abstraction allows it — impossible with inheritance. ## Distinguishing it from neighbours **Bridge vs Adapter.** Same picture, opposite timing and ownership. | | Adapter | Bridge | |---|---|---| | When decided | after the fact, to fix a clash | up front, as a design | | Who owns both interfaces | different parties (third-party/legacy on one side) | you own both | | Goal | make incompatible things work together | keep two axes independently extensible | | Interfaces relation | adapter's exposed interface is dictated by an existing client | abstraction and implementor interfaces are designed together but intentionally at different levels | A useful one-liner: *Adapter makes things work after they are designed; Bridge makes them work before they are.* **Bridge vs Strategy.** Structurally these can be byte-identical. Differences that matter: - Scope: Strategy plugs in one *algorithm* for one operation (sorting order, pricing rule, compression scheme). Bridge plugs in an entire *implementation platform* that most or all of the abstraction's operations route through. - Hierarchies: Bridge explicitly anticipates a hierarchy on *both* sides (refined abstractions as well as concrete implementors). Strategy usually varies only the strategy side; the context is one class. - Family: GoF classifies Bridge as structural (composing a structure) and Strategy as behavioral (varying behaviour). Do not over-invest in the boundary — say which intent you mean. **Bridge vs plain dependency injection.** Injecting an interface is the mechanic; Bridge is the reason. If the injected collaborator represents one of two orthogonal axes of variation and you expect both to grow, calling it a Bridge communicates that expectation. **Bridge vs Abstract Factory.** They pair well: an Abstract Factory can create the right concrete implementor family for a platform, and the abstraction receives it. ## Trade-offs and edge cases - **Cost of the wrong primitive set.** The implementor interface must be the greatest common denominator of every concrete implementation. Pick primitives too low-level and abstraction code becomes verbose; too high-level and some platform cannot implement them, or leaks platform concepts into the interface. This is the hard part of Bridge and it is a design commitment that is expensive to change once several implementors exist. - **Leaky capabilities.** Platforms differ in what they support (transactions, streaming, batching). Handling that with capability flags or optional operations erodes the abstraction. Sometimes the honest answer is that the axes are not truly independent and Bridge is the wrong model. - **Premature Bridge.** With one implementor and no realistic second, you have added a layer, a virtual call and two files for nothing. YAGNI applies. The trigger is a *second* real implementation, or a credible near-term one (e.g. an announced platform migration). - **Testability upside.** The implementor seam is a natural place for a fake, which is often reason enough on its own. - **More than two axes.** Three independent axes need nesting or a different decomposition; Bridge does not generalise gracefully to many dimensions. ## Architecture-scale echo Hexagonal / ports-and-adapters architecture is Bridge thinking at system scale: the domain (abstraction) speaks to ports (implementor interfaces it defines) and infrastructure supplies concrete adapters. Note the naming clash — those "adapters" often play the Bridge role because the domain owns and designs the port interface, whereas true GoF Adapters appear where a port must be reconciled with a vendor SDK's shape.
- When should you not introduce a Bridge?When there is exactly one implementation and no credible second, when the two axes are not actually independent (every abstraction needs platform-specific behaviour anyway), or when the primitive interface would have to be so wide that it merely re-exports one platform's API. The layer costs indirection, files and a virtual call for nothing in those cases.
- How do you choose the implementor's primitive operations?Take the intersection of what all realistic implementors can support, expressed at the lowest level that still lets abstractions be written concisely. Validate by sketching two very different concrete implementors before committing; if one cannot implement a primitive without emulation, the interface is too high-level.
- How does Bridge relate to hexagonal architecture?Hexagonal is the same idea at system scale: the domain defines ports (the implementor interfaces) and infrastructure supplies concrete implementations, so domain and technology evolve independently. The confusing part is naming — hexagonal calls those implementations 'adapters', but structurally they fill Bridge's implementor role because the domain owns the interface.
A wall socket standard. Appliance makers design against the socket; power companies design behind it. Neither has to know the other's catalogue, so new appliances and new generation methods each add one item instead of one per combination. A travel plug adapter, by contrast, is what you improvise afterwards when two standards that were never designed together must meet.
saying these in an interview costs you the question
- 'Bridge and Adapter are the same' — they differ in timing, ownership of the interfaces, and goal.
- Calling every injected interface a Bridge; without two independently varying hierarchies it is just dependency injection.
- Claiming Bridge removes the class explosion with no cost — the shared primitive interface is a hard, sticky design commitment.
- Introducing a Bridge with a single implementation 'for flexibility'.
- Confusing Bridge with Strategy by structure alone rather than by scope of what varies.