OOD Design Patterns
Five patterns cover the large majority of LLD interview extensibility follow-ups. Recognize which one fits before the interviewer asks — naming it unprompted in the design phase is the signal.
Strategy — swap algorithms at runtime
Use when a class needs one of several interchangeable behaviors, chosen at construction or runtime (sorting algorithm, pricing rule, parking-fee calculation).
from abc import ABC, abstractmethod
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: list) -> list: ...
class QuickSort(SortStrategy):
def sort(self, data): ...
class MergeSort(SortStrategy):
def sort(self, data): ...
class Sorter:
def __init__(self, strategy: SortStrategy):
self._strategy = strategy
def sort(self, data):
return self._strategy.sort(data)
Interview tell: "how would you support multiple pricing tiers / multiple parking-fee schemes without an if/elif chain?" → Strategy.
Observer — pub/sub within a process
Use when one event needs to notify multiple independent listeners without the publisher knowing who they are (notification fanout, UI event handling, cache invalidation hooks).
class EventBus:
def __init__(self):
self._listeners: dict[str, list] = {}
def subscribe(self, event: str, fn):
self._listeners.setdefault(event, []).append(fn)
def publish(self, event: str, data=None):
for fn in self._listeners.get(event, []):
fn(data)
Interview tell: "when a spot is vacated, several systems need to react (billing, display board, app notification)" → Observer.
Factory — create objects without specifying the concrete class
Use when object creation logic needs to be centralized and decided by a parameter (channel type, vehicle type, shape type).
class NotificationFactory:
@staticmethod
def create(channel: str) -> "Notification":
if channel == "email": return EmailNotification()
if channel == "sms": return SMSNotification()
raise ValueError(f"Unknown channel: {channel}")
Interview tell: any "given a type string, construct the right object" requirement.
Decorator — wrap behavior without subclassing
Use when you need to layer optional behavior (logging, retries, caching) around an existing interface without creating a subclass explosion for every combination.
class LoggingProcessor:
def __init__(self, processor: "PaymentProcessor"):
self._processor = processor
def process(self, amount):
print(f"Processing {amount}")
result = self._processor.process(amount)
print(f"Result: {result}")
return result
Interview tell: "add logging/retry/caching to this without touching the existing class" → Decorator.
Singleton — one instance globally
Use sparingly — for genuinely single, shared, stateful resources (a config object, a connection pool manager). Overused in interviews as a reflexive answer; naming why a single instance is actually required (not just "there's one config") is the stronger signal.
class Config:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Interview trap: proposing Singleton for something that doesn't need global shared state (e.g., a ParkingSpot — there are many, it's not a singleton) is a negative signal. Singleton is right when there is exactly one of something by requirement, not by convenience.
State (worth knowing beyond the core 5)
Use when an object's behavior changes based on its internal state, and that behavior would otherwise be a large conditional (a vending machine's Idle/HasMoney/Dispensing states, each with different valid operations). Each state is its own class implementing a shared interface; the context object delegates to its current state and can transition to another. See the Vending Machine LLD problem page for a worked example.
Interview Drill
Q: You're designing a vending machine. Inserting money, selecting an item, and dispensing all have different valid next-actions depending on what's already happened. What pattern fits, and why not just a set of boolean flags?
A: State pattern — each state (Idle, HasMoney, Dispensing) is its own class encapsulating which transitions are valid from there, rather than one class with has_money, is_dispensing, item_selected booleans that only make sense in certain combinations. Boolean flags let invalid combinations exist in principle (e.g., is_dispensing=True and has_money=False simultaneously) — the State pattern makes invalid combinations structurally impossible.
Cross-links
[[LLD Interview Approach]] · [[SOLID Principles]]