Bridge Pattern
Separate abstraction from implementation so both can vary independently. An intermediate structural pattern — the planned-upfront decoupling that prevents class explosion when two dimensions of variation cross.
Why Bridge Exists
You are building a shape-rendering library. Shapes come in types (Circle, Square, Triangle) and rendering engines come in variants (SVG, Canvas, OpenGL). The naive approach: create a subclass for every combination — CircleSVG, CircleCanvas, CircleOpenGL, SquareSVG, SquareCanvas... That’s 3 shapes × 3 renderers = 9 classes. Add one shape? 3 more classes. Add one renderer? 3 more classes. The hierarchy explodes multiplicatively.
What goes wrong:
- Class explosion — M shapes × N renderers = M×N classes; adding one dimension multiplies the other
- Parallel inheritance hierarchies — every new shape subclass requires a matching set of renderer subclasses (and vice versa)
- Can’t vary independently — changing how SVG renders circles means editing
CircleSVG, which mixes shape logic with rendering logic - Code duplication — the SVG rendering logic is nearly identical across
CircleSVG,SquareSVG,TriangleSVG; each duplicates the SVG boilerplate
This is the problem Bridge solves — split the single inheritance hierarchy into two independent dimensions: shapes (what to draw) and renderers (how to draw). Connect them with a bridge (composition). Shapes vary on one axis, renderers vary on the other, and you get M + N classes instead of M × N.
What Is Bridge?
Bridge is a structural pattern that separates an abstraction from its implementation so that both can evolve independently. Instead of locking them together in one inheritance hierarchy, Bridge connects them with composition — the abstraction holds a reference to an implementation interface and delegates the platform-specific work to it. New shapes and new renderers can be added without touching each other.
— Gamma, Helm, Johnson, Vlissides (1994)
Key insight: Bridge is about planned separation of two dimensions that both need to grow. Compare this to Adapter, which is an after-the-fact fix for mismatched interfaces. Bridge is designed upfront: you realize “shapes” and “renderers” are orthogonal concerns, so you separate them from the start. The result: M + N classes instead of M × N, and changes in one dimension never ripple to the other.
Pentagon extends Shape, using the existing renderersWebGLRenderer implements Rendererrenderer.drawLine(), renderer.drawArc() — primitive operations on the implementor- Adapter makes two existing incompatible things work together (after the fact).
- Bridge designs them apart from the start so they can grow independently. Adapter = fix; Bridge = architecture.
- If you’re integrating a vendor SDK, that’s Adapter.
- If you’re designing a system with two orthogonal dimensions, that’s Bridge.
Participants & Structure
| Participant | Role | In the Analogy |
|---|---|---|
| Abstraction | Defines the high-level control interface (e.g. draw(), resize()). Holds a reference to an Implementor. Delegates platform-specific work to the implementor. | The remote control — defines buttons (power, volume, channel) but doesn’t contain circuitry. |
| Refined Abstraction | Extends the Abstraction with additional high-level features (e.g. Circle, Square). Each refined abstraction is a variant of what — what shape to draw. | An advanced remote with voice control or gesture control — same buttons + extras. |
| Implementor | An interface declaring the low-level primitive operations (e.g. drawLine(), drawArc(), fillColor()). Does not mirror the Abstraction’s methods — it defines how primitives work on a specific platform. | The standard IR protocol — the spec for how signals are sent to a TV. |
| Concrete Implementor | Implements the Implementor interface for a specific platform (e.g. SVGRenderer, CanvasRenderer). Each concrete implementor is a variant of how. | Sony TV, Samsung TV, LG TV — each interprets IR signals with its own hardware. |
- The gold line from
ShapetoRendereris the bridge itself. - It’s not a class; it’s the relationship. The Abstraction holds an Implementor reference via a field.
- This single field is what prevents the M×N explosion — it connects two independent hierarchies without merging them into one.
The Pattern In Motion
Scenario: Draw a circle using SVG. Then swap the renderer to Canvas without changing the circle. Then add a Triangle without creating any new renderer classes.
Renderer svg = new SVGRenderer()Shape circle = new Circle(svg, 50)circle.draw() โ internally calls renderer.drawArc(cx, cy, 50, 360)SVGRenderer.drawArc() outputs <circle cx="..." cy="..." r="50"/>. The shape expressed its geometry; the renderer did the platform work.Shape circle2 = new Circle(new CanvasRenderer(), 50)circle2.draw() outputs Canvas API calls instead of SVG. Zero edits to Circle.class Triangle extends Shapedraw() calls renderer.drawLine() three times. It works with SVG, Canvas, and any future renderer — zero renderer changes needed.- Both use composition to delegate work to an interface.
- In Strategy, the context delegates one algorithm (which can be swapped by the client).
- In Bridge, the abstraction delegates the platform-specific primitives, and both the abstraction hierarchy and the implementor hierarchy grow independently.
- Strategy = swap one behaviour. Bridge = decouple two parallel dimensions.
You Already Use This
Bridge is subtler in the JDK than patterns like Strategy or Adapter, but it appears wherever the platform separates a “what” hierarchy from a “how” hierarchy connected by delegation.
java.util.logging — Logger (abstraction) delegates to Handler (implementor). You can have multiple logger names (app.service, app.db) and multiple handlers (ConsoleHandler, FileHandler, SocketHandler). Loggers and Handlers vary independently — any logger can use any handler. The logger decides what to log; the handler decides how to output it. java.awt / AWT Peers — AWT components (Button, TextField, Window) are abstractions. Each delegates rendering to a platform-specific peer (ButtonPeer, TextFieldPeer). The peer interface is the implementor; each OS has its own concrete peer (Windows, Mac, X11). Components and platform renderers vary independently. JDBC — java.sql.DriverManager / Connection / Statement form the abstraction. The JDBC driver (MySQL driver, PostgreSQL driver, Oracle driver) is the implementor. Your code uses the JDBC API (abstraction); the vendor-provided driver implements platform-specific SQL. New databases don’t require changing your JDBC code — just plug in a different driver. - Your application code (abstraction) uses
Connection,Statement,ResultSet. - The JDBC driver (implementor) handles MySQL’s wire protocol, PostgreSQL’s binary format, etc.
- You can swap databases by changing one URL string — your SQL code stays the same.
- This is Bridge at industrial scale.
Build It Once
Domain: Shape Rendering. A Renderer implementor interface with primitives (drawLine, drawCircle), two concrete renderers (SVG, Canvas), a Shape abstraction, and two refined shapes (Circle, Square).
- The renderer is injected via the constructor and stored as a field.
- This is intentional — in Bridge, the implementor is typically fixed for the lifetime of the abstraction (unlike Strategy where it may be swapped at runtime).
- If you need runtime swapping, add a
setRenderer()method, but that’s less common for Bridge.
Common Mistakes
renderCircle() and renderSquare(), you’ve just pushed the M×N problem into the implementor. The whole point is that the implementor knows only primitives; the abstraction composes them into shapes.
- If shapes vary but there’s only ever one renderer (SVG), you don’t have two independent dimensions — you have plain inheritance.
- Bridge introduces indirection; don’t add it unless both sides actually vary.
- The “multiplication test”: if you have M×N classes or foresee it, Bridge is justified.
- If it’s just M×1, use simple polymorphism.
- Both wrap/delegate.
- Adapter is an after-the-fact fix โ you have two existing incompatible interfaces and need a translator.
- Bridge is a planned-upfront architectural decision โ you separate two dimensions before they’re ever built.
- If you’re integrating an existing vendor SDK, that’s Adapter.
- If you’re designing a system with shapes × renderers from scratch, that’s Bridge.
- Bridge adds one level of indirection.
- If you then add an AbstractFactory to create the Renderers, and a Decorator on top of the Shapes, you have 3 layers of indirection for something that might be simple.
- Apply YAGNI — use Bridge only when the M×N explosion is real or imminent.
When To Use Bridge
- You have two orthogonal dimensions of variation — e.g. shapes × renderers, devices × platforms, messages × channels
- You see (or foresee) a class explosion of M×N subclasses from combining two hierarchies into one
- Both dimensions need to grow independently — new shapes without touching renderers, new renderers without touching shapes
- You want to swap the implementation at compile/construction time without modifying the abstraction
- You need to share an implementation across multiple abstractions (e.g. one SVGRenderer used by all shapes)
- Only one dimension varies — plain inheritance or Strategy is simpler
- The implementation is already built and incompatible — use Adapter instead (after-the-fact fix)
- You’re adding behaviour to an object — use Decorator (same interface, added features)
- The “multiplication” is only 2×2 and unlikely to grow — the indirection isn’t worth the complexity
| Pattern | Purpose | When designed? | When to pick it |
|---|---|---|---|
| Bridge ← this | Separate two independently varying hierarchies | Upfront (architecture) | Two dimensions cross-multiply (M×N) |
| Adapter | Make an incompatible interface work | After-the-fact (fix) | Existing vendor/legacy with wrong interface |
| Strategy | Swap one algorithm at runtime | Either | One dimension varies (the algorithm), not two parallel hierarchies |
| Abstract Factory | Create families of related objects | Upfront | Need consistent product families (Windows theme vs Mac theme) |
Problems To Solve
Bridge problems test whether you can identify the two orthogonal dimensions, define primitives on the implementor, and compose them in the abstraction without M×N blowup.
| Difficulty | Problem | Key Insight |
|---|---|---|
| Easy | Message × Channel Build a notification system where messages come in types (Alert, Reminder, Promotion) and channels come in variants (Email, SMS, Push). Each message type formats itself differently; each channel delivers differently. Demonstrate that adding a new message type requires zero channel changes, and vice versa. | Dimension 1: Message (what to format). Dimension 2: Channel (how to deliver). The bridge: Message holds a Channel reference. Alert.send() formats urgently then calls channel.deliver(formatted). Without Bridge: 3×3 = 9 classes. With Bridge: 3 + 3 = 6. |
| Medium | Device × OS Build a device control system. Devices: TV, Radio, SmartLight. OS platforms: Android, iOS, WebOS. Each device has controls (power, volume/brightness); each OS has a different low-level API. Implement Bridge so new devices and new platforms are independent. | The Implementor (OS platform) defines primitives: sendCommand(cmd, value). The Abstraction (Device) defines high-level ops: powerOn(), volumeUp(). TV.powerOn() calls platform.sendCommand("POWER", 1). Adding a SmartSpeaker device = 1 class. Adding HarmonyOS = 1 class. |
| Medium | Report × Format Build a reporting system. Reports: SalesReport, InventoryReport, FinanceReport. Formats: PDF, HTML, CSV. Each report queries different data; each format renders differently. Show that adding an XML format requires zero report changes. | The tension: should Formatter know about report types? No โ it should only know primitives (writeHeader(), writeRow(), writeFooter()). Each Report composes these primitives into its specific structure. This tests whether candidates design the implementor at the right level of abstraction. |
| Hard | Shape × Renderer × Theme (3D Bridge) Extend the shape rendering example with a third dimension: Theme (Dark, Light, HighContrast). Now you have shapes, renderers, AND color themes. Implement this without a 3D class explosion. How does Bridge scale to 3+ dimensions? | Tests whether candidates can compose multiple bridges. Shape holds both a Renderer and a Theme. Alternatively, Theme can be injected into the Renderer (Renderer holds Theme). The key insight: Bridge scales to N dimensions by adding one field per dimension to the abstraction, not by multiplying hierarchies. 5 shapes × 3 renderers × 3 themes = 45 classes without Bridge; with Bridge = 5 + 3 + 3 = 11. |
- When asked about Bridge, the interviewer wants to see: (1) the “multiplication test” โ M shapes × N renderers = M×N classes without Bridge, M+N with it; (2) the Implementor defines primitives, not shape-level methods; (3) the distinction from Adapter (upfront vs. after-the-fact); (4) awareness that JDBC and
java.util.loggingare real-world Bridges. - Stand-out answers mention: the Implementor level of abstraction matters (too high = defeats the purpose), Bridge vs.
- Strategy (two hierarchies vs. one swappable algorithm), and how Bridge scales to 3+ dimensions.