StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsBridge

Bridge

A structural pattern that splits two independent dimensions into separate hierarchies joined by one reference, so n plus m classes cover n times m combinations.

Definition

Two shapes in two colors costs four classes under plain inheritance, and the multiplication never stops: three shapes in four colors costs 12, and every new entry on either axis multiplies by the whole other axis. Bridge is the Gang of Four structural pattern that splits the two dimensions into separate class hierarchies connected by a single reference, so each can grow without multiplying the other. The official intent reads: decouple an abstraction from its implementation so that the two can vary independently.


A bridge is really just a field. One hierarchy holds a reference to the other instead of inheriting from it, and that single composition link is the entire load-bearing structure. The math follows immediately: combinations stop being classes and start being object pairs, so n shapes and m colors need n + m classes instead of n × m.

The Multiplication Table

Here's the explosion at its smallest, a drawing library where every shape-color pairing is its own class. Four classes for two shapes and two colors looks tolerable on the screen, and the comment in the code does the forward arithmetic. Before reading on, run the numbers yourself: adding one green and one triangle to this design costs how many new classes?

The answer is five new classes. Green needs GreenCircle and GreenSquare, the triangle needs RedTriangle, BlueTriangle, and GreenTriangle, and the library lands on 9 classes whose bodies are nearly identical. Worse than the count is the duplication: the words "filled red" now exist in three places, so a bug in how red fills means three fixes, or two fixes and one regression.

One Reference Instead of a Dimension

The bridge version pulls color out into its own small hierarchy and hands shapes a reference to it. Color declares the single primitive fill(), Red and Blue implement it, and Shape stores whichever color it's constructed with. Trace Circle(Blue()).draw() and then re-run the prediction from the last section: what do green plus triangle cost now?

Two classes, one per axis. Green never mentions shapes, Triangle never mentions specific colors, and 6 classes now cover all 9 combinations, with the gap widening every time either axis grows. The trace itself is two hops: draw() runs the circle's own logic, reaches through the stored reference for self.color.fill(), and concatenates circle filled blue. The string "filled red" that lived in three classes now lives in zero, assembled instead from two parts that each exist once.

Who Does What

The GoF names are the pattern's hardest part, because "abstraction" and "implementation" here don't mean abstract classes or interface implementations. The Abstraction is the side clients talk to, our Shape, and it holds the reference. RefinedAbstractions are its variants, Circle and Square. The Implementation is the interface on the far side of the reference, Color, with ConcreteImplementations like Red filling it in. Client code composes one from each side and works through the Abstraction.


The arrangement works like a lamp and its bulb. The lamp body handles the switch and the stand, the bulb handles producing light, and the socket between them is the bridge: any lamp takes any bulb, and lamp designers never coordinate with bulb manufacturers. The analogy's limit is worth stating, because a real socket is a standardized fitting that neither side may change, while your Color interface is yours, and every method added to it is a new clause in the contract both hierarchies must honor. The socket only stays simple if you keep it simple.

Bridge: Step-by-Step

Installing the pattern is four moves:

1. Find the Two Things Changing for Different Reasons

The explosion only happens when one hierarchy carries two independent decisions, like shape and color. Naming the two axes out loud is most of the work, because everything after is mechanical.

2. Make One Axis an Interface of Primitives

The Implementation side (Color) becomes an interface of small low-level operations like fill(). Each concrete implementation answers only for itself and knows nothing about shapes.

3. Hand the Abstraction a Reference, Not a Parent

Shape stores a Color field and delegates to it, which is the bridge itself: one composition link replacing an entire dimension of subclasses. Shapes compose primitives into behavior without knowing which color answers.

4. Grow Each Side Without Consulting the Other

A new color is one class that never mentions shapes, and a new shape is one class that never mentions specific colors. The two hierarchies evolve on separate schedules, which is the payoff the whole rearrangement bought.
Without Inheritance: Go and Rust

Go strips the pattern down to its skeleton. The Implementation side is an ordinary interface, the Abstraction side is a struct holding one, and the only Go-specific wrinkle is how shapes share that field: a small embedded struct carries the color reference into Circle and Square, standing in for the abstract base class Go doesn't have. Nothing else changes, which says something about the pattern: it was always composition wearing inheritance-era clothing.


Rust adds a choice the GoF never had to make. Holding the color as Box<dyn Color>, the way the code tab does, picks runtime dispatch: any shape can hold any color decided at runtime, for the cost of an allocation and a vtable hop. Declaring Circle<C: Color> instead monomorphizes the pairing at compile time, free at runtime but fixed per type. Same bridge, two tolls, and choosing between them is ordinary Rust engineering rather than pattern lore.

Bridge vs Adapter, Settled by Timing

You might think Bridge is just Adapter drawn with more boxes, since both put an interface between two things that need to cooperate. Timing is the cleanest separator, and it's the one the classic comparison writeups lean on: an adapter is bolted on after the fact, between two interfaces that already exist and already disagree, while a bridge is drawn before the classes multiply, splitting one design into two hierarchies on purpose. Adapter is a repair, and Bridge is a floor plan.


The other lookalike is Strategy, which also injects an interface and also swaps implementations. The scale differs. Strategy swaps one algorithm behind one method, while Bridge carries an entire family of abstractions on one side and an entire family of implementations on the other, each with its own internal hierarchy. When someone shows you an injected interface, count what varies: one behavior says Strategy, two breeding dimensions say Bridge.

When It's Overkill

Bridge bought its flexibility with indirection, and indirection is only free when it's earning. The speculative version is the common failure: Refactoring.guru's writeup warns that applying the pattern to a highly cohesive class just makes the code more complicated, and "we might need a second backend someday" is how that mistake usually gets approved. Until the second dimension actually exists, every reader pays a two-hierarchy tax to understand what one class used to say.


The test is the multiplication. If you can name real, scheduled entries for both axes, shapes and colors, frontends and renderers, devices and remotes, the bridge starts saving classes at sizes as small as 2 × 2. If one axis holds a single entry with no successor in sight, what you have is a class and an interface it doesn't need yet.

Best Practices

The structure is one field and two hierarchies, and these five details keep it honest:


The pattern pays off only when two dimensions genuinely vary on their own schedules. Splitting a cohesive class that changes for one reason buys an extra hop of indirection and a second hierarchy to maintain, with nothing on the other side of the ledger.

Check Yourself

Two questions before you go. First: the flat design needed 9 classes for three shapes in three colors and the bridge needed 6. Where did the missing three classes' worth of information go? Second: Adapter and Bridge both place an interface between cooperating parts, so what single fact about timing separates them? Both answers are on this page, the second one in its own section.


Then go spot it in the wild, where Bridge hides behind the word "driver". Whenever application code talks to an interface while interchangeable backends implement it, databases behind a query API, renderers behind a graphics API, payment providers behind a checkout API, the two-hierarchy split is doing its quiet work. The grep-able signature in your own codebase is a constructor that accepts an interface which itself has multiple implementations: that parameter is a socket, and the socket is the bridge.

the_class_explosion.py

class RedCircle:
    def draw(self) -> str:
        return "circle filled red"

class BlueCircle:
    def draw(self) -> str:
        return "circle filled blue"

class RedSquare:
    def draw(self) -> str:
        return "square filled red"

class BlueSquare:
    def draw(self) -> str:
        return "square filled blue"

# 2 shapes x 2 colors = 4 classes already.
# Add green: 6 classes. Add triangle too: 9.
# Every new shape multiplies by every color, forever.

# Example usage
print(BlueCircle().draw())  # circle filled blue

bridge.py

from abc import ABC, abstractmethod

class Color(ABC):  # Implementation: the axis that varies independently
    @abstractmethod
    def fill(self) -> str: ...

class Red(Color):  # ConcreteImplementation
    def fill(self) -> str:
        return "red"

class Blue(Color):  # ConcreteImplementation
    def fill(self) -> str:
        return "blue"

class Shape(ABC):  # Abstraction: HOLDS a Color instead of inheriting one
    def __init__(self, color: Color):
        self.color = color

    @abstractmethod
    def draw(self) -> str: ...

class Circle(Shape):  # RefinedAbstraction
    def draw(self) -> str:
        return "circle filled " + self.color.fill()

class Square(Shape):  # RefinedAbstraction
    def draw(self) -> str:
        return "square filled " + self.color.fill()

# 2 shapes + 2 colors = 4 classes covering all 4 combinations.
# Add green: one class. Add triangle too: one more. 6 cover 9.

# Example usage
print(Circle(Blue()).draw())  # circle filled blue
print(Square(Red()).draw())   # square filled red