What if you could build flexible systems that grow without breaking your code?
Why Bridge pattern in LLD? - Purpose & Use Cases
Imagine you are building a drawing app that supports many shapes like circles and squares, and each shape can be drawn in different colors and styles. If you try to create a separate class for every shape-color-style combination, the number of classes will explode quickly, making your code hard to manage.
Manually creating a class for each combination is slow and error-prone. It leads to duplicated code and makes adding new shapes or styles a nightmare because you must change many classes. This rigid design slows development and causes bugs.
The Bridge pattern separates the shape abstraction from its implementation (like color or style). This way, you can mix and match shapes and styles independently, reducing the number of classes and making your code flexible and easy to extend.
class RedCircle {} class BlueCircle {} class RedSquare {} class BlueSquare {}
class Shape { constructor(implementation) { this.implementation = implementation; } } class Circle extends Shape {} class Square extends Shape {} class RedStyle {} class BlueStyle {}
You can add new shapes or styles anytime without rewriting existing code, making your system scalable and easy to maintain.
Think of a remote control (abstraction) that can work with different TV brands (implementations). The Bridge pattern lets you add new remotes or TVs independently without changing the other.
Separates abstraction from implementation to reduce complexity.
Prevents class explosion by allowing independent variation.
Makes adding new features easier and safer.
Practice
Bridge pattern in system design?Solution
Step 1: Understand the Bridge pattern concept
The Bridge pattern is designed to separate an abstraction from its implementation, allowing both to change independently.Step 2: Compare options with the concept
To separate an abstraction from its implementation so they can vary independently correctly states this separation. Other options describe different or incorrect design approaches.Final Answer:
To separate an abstraction from its implementation so they can vary independently -> Option BQuick Check:
Bridge pattern = separation of abstraction and implementation [OK]
- Confusing Bridge with Adapter or Decorator patterns
- Thinking Bridge merges classes instead of separating
- Assuming Bridge enforces rigid inheritance
Solution
Step 1: Recall Bridge pattern structure
Bridge pattern uses composition: the abstraction holds a reference to the implementor interface.Step 2: Analyze each option
class Abstraction { Implementor impl; } correctly shows the required composition. The other options use inheritance (extends) or interface implementation (implements), which defeats the purpose of decoupling via composition.Final Answer:
class Abstraction { Implementor impl; } -> Option CQuick Check:
Bridge uses composition, not inheritance [OK]
- Using inheritance instead of composition
- Confusing implements keyword with composition
- Ignoring the implementor interface role
class DrawingAPI {
drawCircle(x, y, radius) { console.log(`API drawing circle at (${x},${y}) with radius ${radius}`); }
}
class Shape {
constructor(drawingAPI) { this.drawingAPI = drawingAPI; }
draw() {}
}
class Circle extends Shape {
constructor(x, y, radius, drawingAPI) {
super(drawingAPI);
this.x = x; this.y = y; this.radius = radius;
}
draw() {
this.drawingAPI.drawCircle(this.x, this.y, this.radius);
}
}
const api = new DrawingAPI();
const circle = new Circle(5, 10, 7, api);
circle.draw();Solution
Step 1: Trace the draw() method call
The Circle's draw method calls drawingAPI.drawCircle with x=5, y=10, radius=7.Step 2: Check DrawingAPI's drawCircle implementation
It logs: "API drawing circle at (5,10) with radius 7" to the console.Final Answer:
API drawing circle at (5,10) with radius 7 -> Option AQuick Check:
drawCircle logs correct message [OK]
- Assuming drawCircle is undefined
- Confusing output message text
- Ignoring parameter values passed
class Implementor {
operation() { console.log('Implementor operation'); }
}
class Abstraction {
constructor() { this.impl = new Implementor(); }
operation() { this.impl.operation(); }
}
const abs = new Abstraction();
abs.operation();Solution
Step 1: Review Bridge pattern best practice
Abstraction should receive Implementor via constructor injection, not create it internally, to allow flexibility.Step 2: Analyze code's constructor
Abstraction creates Implementor directly, which tightly couples them and breaks Bridge pattern intent.Final Answer:
Abstraction should not create Implementor instance directly -> Option AQuick Check:
Bridge needs abstraction to hold implementor, not create it [OK]
- Ignoring tight coupling caused by direct instantiation
- Thinking concrete class Implementor is always wrong
- Expecting operation to return value unnecessarily
Solution
Step 1: Understand the problem of subclass explosion
If each shape-API pair requires a subclass, the number grows quickly (e.g., CircleOpenGL, CircleDirectX).Step 2: Apply Bridge pattern advantage
Bridge separates shape abstraction from rendering implementation, so shapes and APIs can be combined dynamically without many subclasses.Final Answer:
By allowing shapes and rendering APIs to vary independently without subclass explosion -> Option DQuick Check:
Bridge reduces subclass explosion by separating abstraction and implementation [OK]
- Thinking Bridge merges classes instead of separating
- Assuming inheritance must handle all combinations
- Ignoring dynamic combination of abstraction and implementation
Ready to prove it?
Pass the test and this pattern moves unknown
