Structural

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.

Overview ยท Structural ยท Singleton ยท Factory Method ยท Abstract Factory ยท Builder ยท Prototype ยท Adapter ยท Bridge ยท Composite ยท Decorator ยท Facade ยท Flyweight ยท Proxy ยท Observer ยท Strategy ยท Command ยท Template Method ยท Chain of Resp. ยท State ยท Mediator ยท Iterator ยท Visitor ยท Memento ยท Interpreter
Category: Structural Difficulty: Intermediate Interview: Tier 3 Confused with: Adapter
01
Section One ยท The Problem

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.

Naive approach — class explosion from crossed hierarchies
// โœ— One subclass per combination โ€” grows M ร— N abstract class Shape { abstract void draw(); } // 3 shapes ร— 3 renderers = 9 classes (and growing...) class CircleSVG extends Shape { void draw() { /* SVG circle */ } } class CircleCanvas extends Shape { void draw() { /* Canvas circle */ } } class CircleOpenGL extends Shape { void draw() { /* OpenGL circle */ } } class SquareSVG extends Shape { void draw() { /* SVG square */ } } class SquareCanvas extends Shape { void draw() { /* Canvas square */ } } class SquareOpenGL extends Shape { void draw() { /* OpenGL square */ } } // ... TriangleSVG, TriangleCanvas, TriangleOpenGL // Add Pentagon? โ†’ 3 more classes. Add WebGL renderer? โ†’ 4 more classes.

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
Without Bridge — M × N class explosion
Shape CircleSVG CircleCanvas CircleOpenGL SquareSVG SquareCanvas SquareOpenGL TriangleSVG TriangleCanvas TriangleOpenGL = 9 classes + Pentagon? โ†’ 12 + WebGL? โ†’ 12 (or 16) ✗ M × N subclasses ✗ Parallel hierarchies ✗ Can’t vary shape & renderer independently ✗ SVG logic duplicated across all shapes

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.

02
Section Two ยท The Pattern

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.

GoF Intent: “Decouple an abstraction from its implementation so that the two can vary independently.”
— Gamma, Helm, Johnson, Vlissides (1994)
Analogy — TV remote control and TV brands: A remote control (the abstraction) has buttons: power, volume up, channel next. The TV (the implementation) can be Sony, Samsung, or LG — each with a different internal API. You can swap the TV without changing the remote, and you can upgrade the remote (add voice control) without changing the TV. The remote delegates button presses to whatever TV is plugged in. Remote and TV vary independently — connected by a standard IR protocol (the bridge).

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.

The Abstraction (Shape) defines the high-level API and holds a reference to an Implementor interface (Renderer)
Shape says what to draw; Renderer says how to draw. They’re separate class hierarchies connected by a field.
Adding a new shape (Pentagon) means one class: Pentagon extends Shape, using the existing renderers
Zero renderer classes added. Pentagon works with SVG, Canvas, and OpenGL automatically.
Adding a new renderer (WebGL) means one class: WebGLRenderer implements Renderer
Zero shape classes changed. All existing shapes (Circle, Square, Triangle, Pentagon) work with WebGL automatically.
The Shape calls renderer.drawLine(), renderer.drawArc() — primitive operations on the implementor
The renderer doesn’t know about shapes; the shape doesn’t know about platforms. Maximum independence.
Bridge vs. Adapter:
  • 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.
03
Section Three ยท Anatomy

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.
Bridge — UML Class Diagram
«abstract» Shape - renderer : Renderer + draw() : void Circle + draw() Square + draw() «interface» Renderer + drawLine(x1,y1,x2,y2) + drawArc(cx,cy,r,angle) SVGRenderer + drawLine() CanvasRenderer + drawLine() bridge renderer : Renderer WHAT to draw (Abstraction hierarchy) HOW to draw (Implementor hierarchy) ■ Blue = abstract / interface ■ Green = concrete classes ━ Gold line = the bridge (composition) Total: 2 shapes + 2 renderers = 4 classes Without Bridge: 2 ร— 2 = 4 (same here, but add a 3rd โ†’ 6 vs 5) At 5ร—5: Bridge = 10 classes; Without = 25 classes
The “bridge” is the composition link:
  • The gold line from Shape to Renderer is 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.
04
Section Four ยท How It Works

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.

Step 1 — Create a renderer: Renderer svg = new SVGRenderer()
The implementor knows how to draw primitives (lines, arcs) on SVG. It has no idea what “circle” or “square” means.
Step 2 — Create a shape with the renderer: Shape circle = new Circle(svg, 50)
The Circle stores the renderer (bridge) and its own state (radius=50). It knows what a circle is, but doesn’t know SVG.
Step 3 — Draw: 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.
Step 4 — Swap renderer: Shape circle2 = new Circle(new CanvasRenderer(), 50)
Same Circle class, different renderer. circle2.draw() outputs Canvas API calls instead of SVG. Zero edits to Circle.
Step 5 — Add a new shape: class Triangle extends Shape
Triangle’s draw() calls renderer.drawLine() three times. It works with SVG, Canvas, and any future renderer — zero renderer changes needed.
Before / After — M×N explosion vs. Bridge composition
BEFORE — inheritance (M×N) CircleSVG, CircleCanvas SquareSVG, SquareCanvas TriangleSVG, TriangleCanvas 6 classes (3×2) Add Pentagon + WebGL โ†’ 12 classes (4×3) AFTER — Bridge (M+N) Circle Square Triangle bridge SVGRenderer CanvasRenderer 5 classes (3+2) Add Pentagon + WebGL โ†’ 7 classes (4+3) ✔ M+N vs M×N ✔ Vary independently ✔ No duplication ✔ New shape/renderer = 1 class
The pattern in pseudocode
// โ”€โ”€ Create renderers (implementors) โ”€โ”€ Renderer svg = new SVGRenderer(); Renderer canvas = new CanvasRenderer(); // โ”€โ”€ Create shapes with different renderers (bridge = composition) โ”€โ”€ Shape circleSvg = new Circle(svg, 50); Shape circleCanvas = new Circle(canvas, 50); Shape squareSvg = new Square(svg, 80); // โ”€โ”€ Draw โ€” shape expresses geometry, renderer handles platform โ”€โ”€ circleSvg.draw(); // โ†’ <circle cx="..." r="50"/> circleCanvas.draw(); // โ†’ ctx.arc(cx, cy, 50, 0, 2ฯ€) squareSvg.draw(); // โ†’ <rect x="..." width="80" height="80"/> // โ”€โ”€ Add Triangle โ€” no renderer changes needed โ”€โ”€ Shape tri = new Triangle(canvas, 60); tri.draw(); // โ†’ ctx.moveTo(), ctx.lineTo() ร— 3
Bridge vs. Strategy — similar delegation, different intent:
  • 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.
05
Section Five ยท Java Stdlib

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.

IN JAVA
Example 1 java.util.loggingLogger (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.
Example 2 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.
Example 3 JDBCjava.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.
Stdlib usage — java.util.logging as Bridge
// Abstraction: Logger (what to log) Logger logger = Logger.getLogger("app.service"); // Implementor 1: ConsoleHandler (how to output) logger.addHandler(new ConsoleHandler()); // Implementor 2: FileHandler (different how, same logger) logger.addHandler(new FileHandler("app.log")); // Logger decides WHAT to log; Handler decides HOW to output it logger.info("Order placed"); // โ†’ both console AND file
Stdlib usage — JDBC as Bridge
// Abstraction: JDBC API (Connection, Statement, ResultSet) // Implementor: vendor-specific Driver (MySQL, PostgreSQL, etc.) // Swap implementor by changing only the connection URL Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost/mydb", "user", "pass"); // Same JDBC API, different database โ€” no code changes Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM orders");
JDBC is the most famous Bridge in Java:
  • 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.
06
Section Six ยท Implementation

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).

Java — Bridge Pattern Shape Rendering (core)
// โ”€โ”€ Implementor interface (HOW to draw) โ”€โ”€ interface Renderer { void drawCircle(int cx, int cy, int radius); void drawRect(int x, int y, int w, int h); } // โ”€โ”€ Abstraction (WHAT to draw) โ”€โ”€ abstract class Shape { protected final Renderer renderer; // โ† the bridge protected Shape(Renderer renderer) { this.renderer = renderer; } abstract void draw(); }
The bridge is set at construction time:
  • 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.
07
Section Seven ยท Watch Out

Common Mistakes

Mistake #1 — Implementor mirrors the Abstraction: The Implementor interface should define low-level primitives (drawLine, drawArc, fillColor), not high-level shape methods (drawCircle, drawSquare). If your Renderer interface has 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.
✗ Wrong — implementor mirrors abstraction
// โœ— Renderer knows about shapes โ€” defeats the purpose interface Renderer { void renderCircle(int r); // โ† shape-specific! void renderSquare(int s); // โ† add Triangle? Must edit Renderer }
✔ Correct — implementor defines primitives only
// โœ“ Renderer knows only drawing primitives โ€” shapes compose them interface Renderer { void drawLine(int x1, int y1, int x2, int y2); void drawArc(int cx, int cy, int r, int angle); void fill(String color); } // Add Pentagon โ†’ uses existing drawLine() ร— 5. Zero Renderer changes.
Mistake #2 — Using Bridge when only one dimension varies:
  • 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.
Mistake #3 — Confusing Bridge with Adapter:
  • 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.
Mistake #4 — Too many levels of abstraction:
  • 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.
08
Section Eight ยท Decision Guide

When To Use Bridge

Use Bridge When
  • 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)
Avoid Bridge When
  • 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
Bridge vs. Confused Patterns
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)
Decision Flowchart
Two independent dimensions of variation? No Strategy or Inheritance Yes M×N class explosion (or foreseen)? No Keep it simple Yes Designing from scratch (upfront)? No (fixing) Adapter Yes Bridge
09
Section Nine ยท Practice

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.
Interview Tip:
  • 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.logging are 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.