Skip to content

Bridge Pattern

Separate abstraction from implementation - build flexible systems that can evolve independently!

Bridge Pattern: Separating Abstraction from Implementation

Section titled “Bridge Pattern: Separating Abstraction from Implementation”

Now let’s explore the Bridge Pattern - a powerful structural design pattern that decouples abstraction from implementation.

Imagine you’re building a remote control for your TV. You want to support different types of TVs (Samsung, Sony, LG) and different types of remotes (Basic, Advanced, Smart). Without Bridge Pattern, you’d need a class for every combination (BasicSamsungRemote, AdvancedSamsungRemote, SmartSonyRemote, etc.) - that’s a lot of classes! The Bridge Pattern solves this by separating the remote (abstraction) from the TV (implementation).

The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently. It uses composition instead of inheritance to bridge the abstraction and implementation.

The Bridge Pattern is useful when:

  1. You want to avoid permanent binding - Between abstraction and implementation
  2. Abstraction and implementation should be extensible - Independently by subclassing
  3. Changes in implementation - Should not affect clients
  4. Hide implementation details - From clients
  5. Multiple implementations - Need to share the same abstraction

What Happens If We Don’t Use Bridge Pattern?

Section titled “What Happens If We Don’t Use Bridge Pattern?”

Without the Bridge Pattern, you might:

  • Exponential class explosion - Need classes for every combination (MxN classes)
  • Tight coupling - Abstraction and implementation are tightly bound
  • Hard to extend - Adding new abstraction or implementation requires many changes
  • Violate Open/Closed Principle - Need to modify existing code for new combinations
  • Code duplication - Similar code repeated across multiple classes

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Bridge Pattern works in practice - showing how abstraction and implementation interact:

sequenceDiagram
    participant Client
    participant BasicRemote as BasicRemote (Abstraction)
    participant SamsungTV as SamsungTV (Implementation)
    
    Client->>BasicRemote: power_on()
    activate BasicRemote
    BasicRemote->>SamsungTV: turn_on()
    activate SamsungTV
    SamsungTV-->>BasicRemote: TV turned on
    deactivate SamsungTV
    BasicRemote-->>Client: Success
    deactivate BasicRemote
    
    Note over Client,SamsungTV: Abstraction (Remote) uses<br/>Implementation (TV) through bridge!
    
    Client->>BasicRemote: volume_up()
    activate BasicRemote
    BasicRemote->>SamsungTV: set_volume(current + 1)
    activate SamsungTV
    SamsungTV-->>BasicRemote: Volume increased
    deactivate SamsungTV
    BasicRemote-->>Client: Volume: 5
    deactivate BasicRemote

You’re building a remote control system. You have different types of remotes (Basic, Advanced) and different types of TVs (Samsung, Sony). Without Bridge Pattern:

bad_remote_control.py
# ❌ Without Bridge Pattern - Class explosion!
class BasicSamsungRemote:
"""Basic remote for Samsung TV"""
def power_on(self):
print("Samsung TV: Turning on...")
def power_off(self):
print("Samsung TV: Turning off...")
def volume_up(self):
print("Samsung TV: Volume up")
class AdvancedSamsungRemote:
"""Advanced remote for Samsung TV"""
def power_on(self):
print("Samsung TV: Turning on...")
def power_off(self):
print("Samsung TV: Turning off...")
def volume_up(self):
print("Samsung TV: Volume up")
def mute(self):
print("Samsung TV: Muted")
class BasicSonyRemote:
"""Basic remote for Sony TV"""
def power_on(self):
print("Sony TV: Turning on...")
def power_off(self):
print("Sony TV: Turning off...")
def volume_up(self):
print("Sony TV: Volume up")
class AdvancedSonyRemote:
"""Advanced remote for Sony TV"""
def power_on(self):
print("Sony TV: Turning on...")
def power_off(self):
print("Sony TV: Turning off...")
def volume_up(self):
print("Sony TV: Volume up")
def mute(self):
print("Sony TV: Muted")
# Problems:
# - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)
# - Code duplication - same remote logic repeated
# - Hard to add new remote or TV type
# - Violates Open/Closed Principle

Problems:

  • Exponential class explosion - M remotes × N TVs = M×N classes
  • Code duplication - Same remote logic repeated for each TV
  • Hard to extend - Adding new remote or TV requires many new classes
  • Violates Open/Closed Principle
classDiagram
    class RemoteControl {
        <<abstraction>>
        -tv: TV
        +power_on() void
        +power_off() void
        +volume_up() void
        +volume_down() void
    }
    class BasicRemote {
        +power_on() void
        +power_off() void
        +volume_up() void
        +volume_down() void
    }
    class AdvancedRemote {
        +power_on() void
        +power_off() void
        +volume_up() void
        +volume_down() void
        +mute() void
    }
    class TV {
        <<interface>>
        +turn_on() void
        +turn_off() void
        +set_volume(int) void
    }
    class SamsungTV {
        +turn_on() void
        +turn_off() void
        +set_volume(int) void
    }
    class SonyTV {
        +turn_on() void
        +turn_off() void
        +set_volume(int) void
    }
    
    RemoteControl <|-- BasicRemote : extends
    RemoteControl <|-- AdvancedRemote : extends
    RemoteControl --> TV : has (bridge)
    TV <|.. SamsungTV : implements
    TV <|.. SonyTV : implements
    
    note for RemoteControl "Abstraction - what user interacts with"
    note for TV "Implementation - how it actually works"
bridge_remote_control.py
from abc import ABC, abstractmethod
# Step 1: Define the implementation interface (TV)
class TV(ABC):
"""Implementation interface - how TVs work"""
@abstractmethod
def turn_on(self) -> None:
"""Turn on the TV"""
pass
@abstractmethod
def turn_off(self) -> None:
"""Turn off the TV"""
pass
@abstractmethod
def set_volume(self, level: int) -> None:
"""Set volume level (0-100)"""
pass
# Step 2: Implement concrete TV classes
class SamsungTV(TV):
"""Samsung TV implementation"""
def __init__(self):
self.volume = 0
self.is_on = False
def turn_on(self) -> None:
self.is_on = True
print("Samsung TV: Turning on...")
def turn_off(self) -> None:
self.is_on = False
print("Samsung TV: Turning off...")
def set_volume(self, level: int) -> None:
self.volume = max(0, min(100, level))
print(f"Samsung TV: Volume set to {self.volume}")
class SonyTV(TV):
"""Sony TV implementation"""
def __init__(self):
self.volume = 0
self.is_on = False
def turn_on(self) -> None:
self.is_on = True
print("Sony TV: Turning on...")
def turn_off(self) -> None:
self.is_on = False
print("Sony TV: Turning off...")
def set_volume(self, level: int) -> None:
self.volume = max(0, min(100, level))
print(f"Sony TV: Volume set to {self.volume}")
# Step 3: Define the abstraction (RemoteControl)
class RemoteControl:
"""Abstraction - what user interacts with"""
def __init__(self, tv: TV):
self.tv = tv # Bridge to implementation
def power_on(self) -> None:
"""Turn on the TV"""
self.tv.turn_on()
def power_off(self) -> None:
"""Turn off the TV"""
self.tv.turn_off()
def volume_up(self) -> None:
"""Increase volume"""
# Abstraction doesn't know current volume - implementation handles it
# This is simplified - in real scenario, you'd track volume in abstraction
self.tv.set_volume(50) # Simplified
def volume_down(self) -> None:
"""Decrease volume"""
self.tv.set_volume(30) # Simplified
# Step 4: Extend the abstraction (different remote types)
class BasicRemote(RemoteControl):
"""Basic remote - simple functionality"""
def __init__(self, tv: TV):
super().__init__(tv)
self.volume_level = 50
def volume_up(self) -> None:
"""Increase volume"""
self.volume_level = min(100, self.volume_level + 10)
self.tv.set_volume(self.volume_level)
def volume_down(self) -> None:
"""Decrease volume"""
self.volume_level = max(0, self.volume_level - 10)
self.tv.set_volume(self.volume_level)
class AdvancedRemote(RemoteControl):
"""Advanced remote - extended functionality"""
def __init__(self, tv: TV):
super().__init__(tv)
self.volume_level = 50
self.is_muted = False
def volume_up(self) -> None:
"""Increase volume"""
if self.is_muted:
self.is_muted = False
self.volume_level = min(100, self.volume_level + 10)
self.tv.set_volume(self.volume_level)
def volume_down(self) -> None:
"""Decrease volume"""
self.volume_level = max(0, self.volume_level - 10)
self.tv.set_volume(self.volume_level)
def mute(self) -> None:
"""Mute/unmute TV"""
self.is_muted = not self.is_muted
if self.is_muted:
self.tv.set_volume(0)
print("TV: Muted")
else:
self.tv.set_volume(self.volume_level)
print("TV: Unmuted")
# Usage - Clean and flexible!
def main():
# Create TVs (implementations)
samsung_tv = SamsungTV()
sony_tv = SonyTV()
# Create remotes (abstractions) with different TVs
basic_samsung = BasicRemote(samsung_tv)
advanced_samsung = AdvancedRemote(samsung_tv)
basic_sony = BasicRemote(sony_tv)
advanced_sony = AdvancedRemote(sony_tv)
print("=== Basic Remote with Samsung TV ===")
basic_samsung.power_on()
basic_samsung.volume_up()
basic_samsung.volume_down()
basic_samsung.power_off()
print("\n=== Advanced Remote with Sony TV ===")
advanced_sony.power_on()
advanced_sony.volume_up()
advanced_sony.mute()
advanced_sony.power_off()
print("\n✅ Bridge Pattern allows independent variation of remotes and TVs!")
if __name__ == "__main__":
main()

Real-World Software Example: Shape Rendering System

Section titled “Real-World Software Example: Shape Rendering System”

Now let’s see a realistic software example - a graphics system that needs to render different shapes using different rendering engines.

You’re building a graphics application that needs to render shapes (Circle, Rectangle) using different rendering engines (Vector, Raster). Without Bridge Pattern:

bad_shape_rendering.py
# ❌ Without Bridge Pattern - Class explosion!
class VectorCircle:
"""Circle rendered using vector graphics"""
def render(self):
print("Rendering Circle using Vector graphics")
class RasterCircle:
"""Circle rendered using raster graphics"""
def render(self):
print("Rendering Circle using Raster graphics")
class VectorRectangle:
"""Rectangle rendered using vector graphics"""
def render(self):
print("Rendering Rectangle using Vector graphics")
class RasterRectangle:
"""Rectangle rendered using raster graphics"""
def render(self):
print("Rendering Rectangle using Raster graphics")
# Problems:
# - Need 2 shapes × 2 renderers = 4 classes
# - Adding new shape (Triangle) = 2 more classes
# - Adding new renderer (SVG) = 3 more classes
# - Exponential growth!

Problems:

  • Exponential class explosion - M shapes × N renderers = M×N classes
  • Hard to extend - Adding new shape or renderer requires many classes
  • Code duplication - Similar rendering logic repeated
bridge_shape_rendering.py
from abc import ABC, abstractmethod
# Step 1: Define the implementation interface (Renderer)
class Renderer(ABC):
"""Implementation interface - how shapes are rendered"""
@abstractmethod
def render_circle(self, radius: float) -> None:
"""Render a circle"""
pass
@abstractmethod
def render_rectangle(self, width: float, height: float) -> None:
"""Render a rectangle"""
pass
# Step 2: Implement concrete renderer classes
class VectorRenderer(Renderer):
"""Vector graphics renderer"""
def render_circle(self, radius: float) -> None:
print(f"Drawing Circle (radius: {radius}) using Vector graphics")
def render_rectangle(self, width: float, height: float) -> None:
print(f"Drawing Rectangle ({width}x{height}) using Vector graphics")
class RasterRenderer(Renderer):
"""Raster graphics renderer"""
def render_circle(self, radius: float) -> None:
print(f"Rasterizing Circle (radius: {radius}) using pixels")
def render_rectangle(self, width: float, height: float) -> None:
print(f"Rasterizing Rectangle ({width}x{height}) using pixels")
# Step 3: Define the abstraction (Shape)
class Shape:
"""Abstraction - what user interacts with"""
def __init__(self, renderer: Renderer):
self.renderer = renderer # Bridge to implementation
def draw(self) -> None:
"""Draw the shape"""
raise NotImplementedError
# Step 4: Extend the abstraction (different shapes)
class Circle(Shape):
"""Circle shape"""
def __init__(self, renderer: Renderer, radius: float):
super().__init__(renderer)
self.radius = radius
def draw(self) -> None:
"""Draw the circle"""
self.renderer.render_circle(self.radius)
class Rectangle(Shape):
"""Rectangle shape"""
def __init__(self, renderer: Renderer, width: float, height: float):
super().__init__(renderer)
self.width = width
self.height = height
def draw(self) -> None:
"""Draw the rectangle"""
self.renderer.render_rectangle(self.width, self.height)
# Usage - Clean and flexible!
def main():
# Create renderers (implementations)
vector_renderer = VectorRenderer()
raster_renderer = RasterRenderer()
# Create shapes (abstractions) with different renderers
vector_circle = Circle(vector_renderer, 5.0)
raster_circle = Circle(raster_renderer, 5.0)
vector_rect = Rectangle(vector_renderer, 10.0, 20.0)
raster_rect = Rectangle(raster_renderer, 10.0, 20.0)
print("=== Vector Rendering ===")
vector_circle.draw()
vector_rect.draw()
print("\n=== Raster Rendering ===")
raster_circle.draw()
raster_rect.draw()
print("\n✅ Bridge Pattern allows independent variation of shapes and renderers!")
if __name__ == "__main__":
main()

The Bridge Pattern is typically implemented using composition (Object Bridge), but there are considerations:

1. Object Bridge (Composition) - Preferred

Section titled “1. Object Bridge (Composition) - Preferred”

Uses composition - abstraction contains reference to implementation:

object_bridge.py
# Object Bridge - uses composition (preferred)
class Implementation:
def operation(self):
return "Implementation"
class Abstraction:
def __init__(self, impl: Implementation):
self.impl = impl # Composition
def operation(self):
return self.impl.operation()

Pros: Flexible, can change implementation at runtime
Cons: Requires implementation instance


Use Bridge Pattern when:

You want to avoid permanent binding - Between abstraction and implementation
Abstraction and implementation should vary independently - By subclassing
Changes in implementation - Should not affect clients
Hide implementation details - From clients
Multiple implementations - Need to share the same abstraction
Runtime binding - Want to switch implementations at runtime

Don’t use Bridge Pattern when:

Simple one-to-one relationship - If abstraction and implementation are tightly coupled
Performance critical - Bridge adds indirection (usually negligible)
Over-engineering - Don’t add complexity for simple cases
Abstraction is stable - If abstraction rarely changes, direct inheritance might be simpler


bridge_vs_adapter.py
# ❌ Confusing Bridge with Adapter
# Bridge Pattern: Separates abstraction from implementation
# - Used when you want to avoid permanent binding
# - Abstraction and implementation vary independently
# - Planned separation from the start
# Adapter Pattern: Makes incompatible interfaces compatible
# - Used when you have incompatible interfaces
# - Adapts existing code to work together
# - Reactive solution to incompatibility
# Example: Bridge
class Shape:
def __init__(self, renderer: Renderer): # Planned separation
self.renderer = renderer
# Example: Adapter
class Adapter:
def __init__(self, incompatible_class): # Making incompatible work
self.adaptee = incompatible_class

Mistake 2: Using Inheritance Instead of Composition

Section titled “Mistake 2: Using Inheritance Instead of Composition”
wrong_bridge.py
# ❌ Bad: Using inheritance instead of composition
class VectorCircle(Circle, VectorRenderer): # Multiple inheritance - not Bridge!
pass
# ✅ Good: Using composition (Bridge Pattern)
class Circle:
def __init__(self, renderer: Renderer): # Composition
self.renderer = renderer
tight_coupling.py
# ❌ Bad: Abstraction knows too much about implementation
class Circle:
def __init__(self, renderer: Renderer):
self.renderer = renderer
def draw(self):
if isinstance(self.renderer, VectorRenderer):
# Bad: Checking implementation type!
self.renderer.render_circle_vector(self.radius)
elif isinstance(self.renderer, RasterRenderer):
# Bad: Implementation details leak into abstraction!
self.renderer.render_circle_raster(self.radius)
# ✅ Good: Abstraction only uses interface
class Circle:
def __init__(self, renderer: Renderer):
self.renderer = renderer
def draw(self):
# Good: Only uses interface, doesn't know implementation details
self.renderer.render_circle(self.radius)

  1. Decoupling - Abstraction and implementation are separated
  2. Independent Variation - Can vary abstraction and implementation independently
  3. No Class Explosion - M abstractions + N implementations = M + N classes (not M×N)
  4. Runtime Binding - Can switch implementations at runtime
  5. Open/Closed Principle - Open for extension, closed for modification
  6. Hide Implementation - Implementation details hidden from clients

Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. It uses composition to bridge the abstraction and implementation.

  • Avoid class explosion - M abstractions × N implementations = M×N classes without Bridge
  • Independent variation - Change abstraction without changing implementation
  • Runtime binding - Switch implementations at runtime
  • Decoupling - Separate what you use from how it works
  • Open/Closed Principle - Open for extension, closed for modification
  1. Define implementation interface - How things work
  2. Implement concrete classes - Different implementations
  3. Define abstraction - What user interacts with
  4. Bridge them - Abstraction contains reference to implementation
  5. Extend independently - Add new abstractions or implementations independently
Abstraction → Implementation Interface → Concrete Implementation
  • Abstraction - What user interacts with (e.g., RemoteControl)
  • Implementation Interface - How things work (e.g., TV interface)
  • Concrete Implementation - Actual implementation (e.g., SamsungTV, SonyTV)
  • Bridge - Composition link between abstraction and implementation
class Implementation:
def operation(self): pass
class Abstraction:
def __init__(self, impl: Implementation):
self.impl = impl # Bridge
def operation(self):
return self.impl.operation()

✅ Avoid permanent binding between abstraction and implementation
✅ Abstraction and implementation should vary independently
✅ Multiple implementations need to share same abstraction
✅ Want runtime binding
✅ Facing class explosion (M×N problem)

❌ Simple one-to-one relationship
❌ Performance critical (adds indirection)
❌ Over-engineering simple cases
❌ Abstraction is stable

  • Bridge Pattern = Separates abstraction from implementation
  • Abstraction = What user interacts with
  • Implementation = How it actually works
  • Bridge = Composition link between them
  • Benefit = Independent variation, no class explosion
  • Use Case = When you have M abstractions and N implementations
class Implementation:
def operation(self): pass
class Abstraction:
def __init__(self, impl: Implementation):
self.impl = impl
def operation(self):
return self.impl.operation()
# Usage
impl = ConcreteImplementation()
abstraction = ConcreteAbstraction(impl)
abstraction.operation()
  • Bridge Pattern separates abstraction from implementation
  • It uses composition, not inheritance
  • It prevents class explosion (M×N → M+N)
  • Use it when you need independent variation
  • It’s about decoupling, not just wrapping!

What to say:

“Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. It uses composition to bridge the abstraction and implementation, preventing class explosion.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Avoid class explosion - M abstractions × N implementations = M×N classes
  • Independent variation - Want to change abstraction without changing implementation
  • Runtime binding - Want to switch implementations at runtime
  • Decoupling - Separate what you use from how it works

Example scenario to give:

“I’d use Bridge Pattern when building a graphics system with different shapes (Circle, Rectangle) and different rendering engines (Vector, Raster). Without Bridge, I’d need Circle×Vector, Circle×Raster, Rectangle×Vector, Rectangle×Raster = 4 classes. With Bridge, I need 2 shapes + 2 renderers = 4 classes, but they can vary independently.”

Must discuss:

  • Bridge: Planned separation, abstraction and implementation vary independently
  • Adapter: Reactive solution, makes incompatible interfaces compatible
  • Key difference: Bridge is planned, Adapter is reactive

Example to give:

“Bridge Pattern is used when you plan to separate abstraction from implementation from the start. Adapter Pattern is used when you have existing incompatible interfaces that need to work together. Bridge is about design, Adapter is about compatibility.”

Benefits to mention:

  • No class explosion - M + N instead of M×N classes
  • Independent variation - Change abstraction or implementation independently
  • Runtime binding - Switch implementations at runtime
  • Decoupling - Abstraction and implementation are separate
  • Open/Closed Principle - Open for extension, closed for modification

Trade-offs to acknowledge:

  • Complexity - Adds a layer of indirection
  • Performance - Small overhead (usually negligible)
  • Over-engineering risk - Can be overkill for simple cases

Q: “What’s the difference between Bridge Pattern and Adapter Pattern?”

A:

“Bridge Pattern is a planned separation of abstraction from implementation, allowing them to vary independently. Adapter Pattern is a reactive solution that makes incompatible interfaces compatible. Bridge is about design and flexibility, Adapter is about compatibility and integration.”

Q: “When would you use Bridge vs direct inheritance?”

A:

“I use Bridge when I need independent variation of abstraction and implementation. If I have M types of remotes and N types of TVs, inheritance would require M×N classes. Bridge requires only M + N classes. I use direct inheritance when there’s a stable one-to-one relationship.”

Q: “How does Bridge Pattern relate to SOLID principles?”

A:

“Bridge Pattern primarily supports the Open/Closed Principle - you can extend functionality (add new abstractions or implementations) without modifying existing code. It also supports Single Responsibility Principle by separating abstraction concerns from implementation concerns. Additionally, it helps with Dependency Inversion Principle by having abstractions depend on implementation interfaces rather than concrete implementations.”

Before your interview, make sure you can:

  • Define Bridge Pattern clearly in one sentence
  • Explain when to use it (with examples showing class explosion)
  • Describe Bridge vs Adapter Pattern
  • Implement Bridge Pattern from scratch
  • Compare with other structural patterns (Adapter, Decorator)
  • List benefits and trade-offs
  • Identify common mistakes
  • Give 2-3 real-world examples
  • Connect to SOLID principles
  • Discuss when NOT to use it
  • Explain M×N class explosion problem

Remember: Bridge Pattern is about separating abstraction from implementation - allowing them to vary independently and preventing class explosion! 🌉