Skip to main content
Design Patterns Map - All 23 Gang of Four Patterns
Time to Master: 2-3 hours | Prerequisites: OOP basics | Interview Frequency: Very High
Pattern Selection Tip: Don’t force patterns! If simple code solves the problem, use simple code. Patterns add complexity that must be justified with real benefits.

πŸ“Š Pattern Overview

Design patterns are proven solutions to common software design problems. There are 23 classic β€œGang of Four” patterns divided into 3 categories:

Creational (5)

How objects are createdSingleton, Factory, Abstract Factory, Builder, Prototype

Structural (7)

How objects are composedAdapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

Behavioral (11)

How objects communicateStrategy, Observer, Command, State, Template, Iterator, Mediator, Chain of Responsibility, Visitor, Memento, Interpreter
Interview Focus: You don’t need to memorize all 23! Focus on: Singleton, Factory, Strategy, Observer, State, Decorator, Command - these cover 90% of LLD interviews.

🎯 Pattern Decision Tree

Use this to quickly identify which pattern fits your problem:
Need to create objects?
β”œβ”€β”€ Only ONE instance needed? ───────────────────► SINGLETON
β”œβ”€β”€ Don't know exact class at compile time? ────► FACTORY
β”œβ”€β”€ Object has many optional parameters? ───────► BUILDER
└── Need to copy existing objects? ─────────────► PROTOTYPE

Need to compose/structure objects?
β”œβ”€β”€ Incompatible interfaces? ───────────────────► ADAPTER
β”œβ”€β”€ Add behavior without subclassing? ──────────► DECORATOR
β”œβ”€β”€ Simplify complex subsystem? ────────────────► FACADE
β”œβ”€β”€ Tree-like hierarchies? ─────────────────────► COMPOSITE
└── Control access to object? ──────────────────► PROXY

Need to manage object behavior/communication?
β”œβ”€β”€ Swap algorithms at runtime? ────────────────► STRATEGY
β”œβ”€β”€ Notify multiple objects of changes? ────────► OBSERVER
β”œβ”€β”€ Object behavior changes with state? ────────► STATE
β”œβ”€β”€ Queue and undo operations? ─────────────────► COMMAND
β”œβ”€β”€ Traverse collection without exposing internals? β–Ί ITERATOR
└── Reduce coupling between objects? ───────────► MEDIATOR

πŸ”΅ Creational Patterns

How objects are created

Singleton

One instance only

Factory

Create without specifying class

Builder

Step-by-step construction

1. Singleton Pattern

When to Use: Database connections, Configuration, Logging, Thread pools, Caches - anything where only ONE instance should exist.
Ensures a class has only one instance with global access.
import threading

class DatabaseConnection:
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:  # Double-checked locking
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialize()
        return cls._instance
    
    def _initialize(self):
        self.connection = self._create_connection()
    
    def _create_connection(self):
        return "Database Connection"

# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
assert db1 is db2  # Same instance
Use When: Database connections, Configuration, Logging, Thread pools, Caches
Singleton Anti-patterns:
  • Makes unit testing harder (global state)
  • Can hide dependencies
  • Consider dependency injection instead for better testability

2. Factory Pattern

Creates objects without specifying exact classes.
from abc import ABC, abstractmethod

# Product interface
class Notification(ABC):
    @abstractmethod
    def send(self, message: str):
        pass

# Concrete products
class EmailNotification(Notification):
    def send(self, message: str):
        print(f"Sending email: {message}")

class SMSNotification(Notification):
    def send(self, message: str):
        print(f"Sending SMS: {message}")

class PushNotification(Notification):
    def send(self, message: str):
        print(f"Sending push: {message}")

# Factory
class NotificationFactory:
    @staticmethod
    def create(channel: str) -> Notification:
        factories = {
            "email": EmailNotification,
            "sms": SMSNotification,
            "push": PushNotification
        }
        if channel not in factories:
            raise ValueError(f"Unknown channel: {channel}")
        return factories[channel]()

# Usage
notification = NotificationFactory.create("email")
notification.send("Hello!")
Use When: Object creation logic is complex, Multiple types share interface

3. Builder Pattern

Constructs complex objects step by step.
class Pizza:
    def __init__(self):
        self.size = None
        self.cheese = False
        self.pepperoni = False
        self.mushrooms = False
    
    def __str__(self):
        toppings = []
        if self.cheese: toppings.append("cheese")
        if self.pepperoni: toppings.append("pepperoni")
        if self.mushrooms: toppings.append("mushrooms")
        return f"{self.size} pizza with {', '.join(toppings)}"

class PizzaBuilder:
    def __init__(self):
        self.pizza = Pizza()
    
    def set_size(self, size: str):
        self.pizza.size = size
        return self
    
    def add_cheese(self):
        self.pizza.cheese = True
        return self
    
    def add_pepperoni(self):
        self.pizza.pepperoni = True
        return self
    
    def add_mushrooms(self):
        self.pizza.mushrooms = True
        return self
    
    def build(self) -> Pizza:
        return self.pizza

# Usage - fluent interface
pizza = (PizzaBuilder()
         .set_size("large")
         .add_cheese()
         .add_pepperoni()
         .build())
print(pizza)  # large pizza with cheese, pepperoni
Use When: Object has many optional parameters, Step-by-step construction

Structural Patterns

How objects are composed

4. Adapter Pattern

Makes incompatible interfaces work together.
# Existing interface (what client expects)
class ModernPayment(ABC):
    @abstractmethod
    def pay(self, amount: float) -> bool:
        pass

# Legacy system (what we have)
class LegacyPaymentGateway:
    def make_payment(self, dollars: int, cents: int) -> str:
        return f"Paid ${dollars}.{cents:02d}"

# Adapter
class PaymentAdapter(ModernPayment):
    def __init__(self, legacy: LegacyPaymentGateway):
        self.legacy = legacy
    
    def pay(self, amount: float) -> bool:
        dollars = int(amount)
        cents = int((amount - dollars) * 100)
        result = self.legacy.make_payment(dollars, cents)
        return "Paid" in result

# Usage
legacy_gateway = LegacyPaymentGateway()
payment = PaymentAdapter(legacy_gateway)
payment.pay(29.99)
Use When: Integrating legacy code, Third-party library integration

5. Decorator Pattern

Adds behavior to objects dynamically.
class Coffee(ABC):
    @abstractmethod
    def cost(self) -> float:
        pass
    
    @abstractmethod
    def description(self) -> str:
        pass

class SimpleCoffee(Coffee):
    def cost(self) -> float:
        return 2.0
    
    def description(self) -> str:
        return "Coffee"

class CoffeeDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee

class MilkDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.5
    
    def description(self) -> str:
        return self._coffee.description() + ", Milk"

class SugarDecorator(CoffeeDecorator):
    def cost(self) -> float:
        return self._coffee.cost() + 0.2
    
    def description(self) -> str:
        return self._coffee.description() + ", Sugar"

# Usage - stack decorators
coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(f"{coffee.description()}: ${coffee.cost()}")
# Coffee, Milk, Sugar: $2.7
Use When: Add features without subclassing, Compose behaviors

6. Facade Pattern

Provides simple interface to complex subsystem.
# Complex subsystems
class VideoFile:
    def __init__(self, filename):
        self.filename = filename

class CodecFactory:
    def extract(self, file):
        return "codec"

class AudioMixer:
    def fix(self, audio):
        return "fixed_audio"

class VideoConverter:
    def convert(self, video, codec):
        return "converted_video"

# Facade - simple interface
class VideoConversionFacade:
    def convert(self, filename: str, format: str) -> str:
        file = VideoFile(filename)
        codec = CodecFactory().extract(file)
        audio = AudioMixer().fix(file)
        result = VideoConverter().convert(file, codec)
        return f"Converted {filename} to {format}"

# Usage - client doesn't know about subsystems
facade = VideoConversionFacade()
result = facade.convert("video.mp4", "avi")
Use When: Simplify complex systems, Provide unified API

Behavioral Patterns

How objects communicate

7. Strategy Pattern

Defines family of interchangeable algorithms.
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> str:
        pass

class CreditCardPayment(PaymentStrategy):
    def __init__(self, card_number: str):
        self.card_number = card_number
    
    def pay(self, amount: float) -> str:
        return f"Paid ${amount} with card ending {self.card_number[-4:]}"

class PayPalPayment(PaymentStrategy):
    def __init__(self, email: str):
        self.email = email
    
    def pay(self, amount: float) -> str:
        return f"Paid ${amount} via PayPal ({self.email})"

class CryptoPayment(PaymentStrategy):
    def __init__(self, wallet: str):
        self.wallet = wallet
    
    def pay(self, amount: float) -> str:
        return f"Paid ${amount} in crypto to {self.wallet[:8]}..."

# Context
class ShoppingCart:
    def __init__(self):
        self.items = []
        self.payment_strategy = None
    
    def set_payment_strategy(self, strategy: PaymentStrategy):
        self.payment_strategy = strategy
    
    def checkout(self) -> str:
        total = sum(item.price for item in self.items)
        return self.payment_strategy.pay(total)

# Usage - swap strategies at runtime
cart = ShoppingCart()
cart.set_payment_strategy(CreditCardPayment("1234567890123456"))
cart.checkout()
Use When: Multiple algorithms for same task, Switch behavior at runtime

8. Observer Pattern

Notifies multiple objects about state changes.
class Subject(ABC):
    @abstractmethod
    def attach(self, observer):
        pass
    
    @abstractmethod
    def detach(self, observer):
        pass
    
    @abstractmethod
    def notify(self):
        pass

class Observer(ABC):
    @abstractmethod
    def update(self, subject):
        pass

class Stock(Subject):
    def __init__(self, symbol: str, price: float):
        self.symbol = symbol
        self._price = price
        self._observers = []
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    def price(self, value):
        self._price = value
        self.notify()
    
    def attach(self, observer):
        self._observers.append(observer)
    
    def detach(self, observer):
        self._observers.remove(observer)
    
    def notify(self):
        for observer in self._observers:
            observer.update(self)

class StockAlert(Observer):
    def __init__(self, name: str):
        self.name = name
    
    def update(self, subject):
        print(f"{self.name}: {subject.symbol} is now ${subject.price}")

# Usage
apple = Stock("AAPL", 150.0)
investor1 = StockAlert("Investor 1")
investor2 = StockAlert("Investor 2")

apple.attach(investor1)
apple.attach(investor2)

apple.price = 155.0  # Both investors notified
Use When: One-to-many dependencies, Event systems

9. State Pattern

Object behavior changes based on internal state.
class OrderState(ABC):
    @abstractmethod
    def next(self, order):
        pass
    
    @abstractmethod
    def prev(self, order):
        pass
    
    @abstractmethod
    def status(self) -> str:
        pass

class PendingState(OrderState):
    def next(self, order):
        order.state = ProcessingState()
    
    def prev(self, order):
        print("Already at initial state")
    
    def status(self) -> str:
        return "Pending"

class ProcessingState(OrderState):
    def next(self, order):
        order.state = ShippedState()
    
    def prev(self, order):
        order.state = PendingState()
    
    def status(self) -> str:
        return "Processing"

class ShippedState(OrderState):
    def next(self, order):
        order.state = DeliveredState()
    
    def prev(self, order):
        order.state = ProcessingState()
    
    def status(self) -> str:
        return "Shipped"

class DeliveredState(OrderState):
    def next(self, order):
        print("Already delivered")
    
    def prev(self, order):
        print("Cannot go back from delivered")
    
    def status(self) -> str:
        return "Delivered"

class Order:
    def __init__(self):
        self.state = PendingState()
    
    def next_state(self):
        self.state.next(self)
    
    def prev_state(self):
        self.state.prev(self)
    
    def status(self):
        return self.state.status()

# Usage
order = Order()
print(order.status())  # Pending
order.next_state()
print(order.status())  # Processing
order.next_state()
print(order.status())  # Shipped
Use When: Object behavior depends on state, State-specific logic is complex

Pattern Selection Guide

ProblemPattern
Need single instanceSingleton
Create objects without knowing classFactory
Complex object constructionBuilder
Integrate incompatible interfacesAdapter
Add features dynamicallyDecorator
Simplify complex subsystemFacade
Interchangeable algorithmsStrategy
Notify of state changesObserver
Behavior changes with stateState

πŸ”₯ Top Patterns for Interviews

These patterns appear most frequently in LLD interviews:

Must-Know (80% of interviews)

PatternCommon Use Cases
FactoryVehicle types, Payment methods, Notification channels
StrategyPayment processing, Shipping calculation, Pricing rules
ObserverStock price alerts, Order status, Pub/Sub systems
StateOrder lifecycle, Elevator, Vending machine, ATM
SingletonDatabase, Configuration, Logger

Good to Know (20% of interviews)

PatternCommon Use Cases
BuilderComplex configurations, Query builders
DecoratorMiddleware, Permission layers
AdapterLegacy integration, Third-party APIs
CommandUndo/Redo, Transaction logs
FacadeComplex subsystem APIs

πŸ“š Pattern Diagrams

SINGLETON                    FACTORY                      STRATEGY
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Singleton  β”‚             β”‚   Creator   β”‚             β”‚   Context   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ -instance   β”‚             β”‚+createProd()│──creates──▷│ -strategy   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚+getInstance β”‚                   β”‚                    β”‚+setStrategy β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β–½                    β”‚+execute()   β”‚
                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                           β”‚   Product   β”‚                    β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β–½
                                 β–³                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”            β”‚ <<interface>>  β”‚
                     β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”       β”‚   Strategy     β”‚
                     β”‚ProductA β”‚   β”‚ProductB β”‚       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚+execute()      β”‚
                                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                            β–³
                                                     β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
                                                 β”Œβ”€β”€β”€β”΄β”€β”€β”€β”   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
                                                 β”‚StratA β”‚   β”‚StratB β”‚
                                                 β””β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”˜

OBSERVER                     STATE                        DECORATOR
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Subject   β”‚             β”‚   Context   β”‚             β”‚  Component  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ -observers  β”‚             β”‚ -state      β”‚             β”‚+operation() β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
β”‚+attach()    β”‚             β”‚+setState()  β”‚                    β–³
β”‚+notify()    β”‚             β”‚+request()   β”‚             β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜        β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
       β”‚                           β”‚               β”‚Concrete β”‚   β”‚Decoratorβ”‚
       β–½                           β–½               β”‚Componentβ”‚   β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚-wrapped β”‚
β”‚ <<interface>>  β”‚         β”‚ <<interface>> β”‚                     β”‚+op()    β”‚
β”‚   Observer     β”‚         β”‚    State      β”‚                     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€         β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                          β–³
β”‚+update()       β”‚         β”‚+handle()      β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”Œβ”€β”€β”€β”΄β”€β”€β”€β”   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
       β–³                          β–³                        β”‚DecorA β”‚   β”‚DecorB β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”               β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”                 β””β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚ConcObsβ”‚           β”Œβ”€β”€β”€β”΄β”€β”€β”€β”   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
   β””β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚StateA β”‚   β”‚StateB β”‚
                       β””β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“ Pattern Comparison Table

PatternIntentUse WhenAvoid When
SingletonOne instance globallyDB connection, ConfigNeed testability, multiple instances
FactoryCreate objects without specifying classMultiple product typesOnly one concrete class
BuilderComplex object constructionMany optional parametersSimple objects
StrategySwap algorithms at runtimeMultiple ways to do somethingOnly one algorithm
ObserverNotify subscribers of changesDecoupled event handlingFew, known subscribers
StateObject behavior changes with stateFinite state machineSimple conditionals suffice
DecoratorAdd behavior dynamicallyLayered functionalityInheritance works fine
FacadeSimplify complex subsystemsComplex library integrationSimple interfaces already
CommandEncapsulate requests as objectsUndo/redo, queuingSimple direct calls

πŸ’‘ Interview Tips

  • Do: β€œI’ll use Factory here because we need to create vehicles without knowing the exact type at compile time”
  • Don’t: β€œLet me apply all 23 GoF patterns to show I know them”
Pro tip: Wait for the right moment. If you’re designing payment methods, naturally mention Strategy pattern.
Every pattern has costs - always mention them:
PatternBenefitCost
SingletonGlobal access, one instanceHard to test, hidden dependencies
FactoryDecoupled creationMore abstraction layers
ObserverLoose couplingMemory leaks if not detached
StrategySwappable algorithmsMore classes to manage
DecoratorFlexible compositionComplex debugging
Always explain why the benefit outweighs the cost for YOUR use case.
Patterns often work together:
  • Factory + Strategy: Create strategies via factory
  • Observer + Singleton: Global event bus
  • State + Factory: Create states via factory
  • Decorator + Factory: Create decorators dynamically
  • Command + Memento: Undo with snapshots
  • β€œWhy did you use X pattern here?”
  • β€œWhat’s the difference between Strategy and State?”
  • β€œHow would you add a new payment method?” (Strategy)
  • β€œHow would you implement undo/redo?” (Command + Memento)
  • β€œHow would you prevent double instantiation?” (Singleton with locking)
Don’t over-engineer! Use patterns when they solve real problems, not to show off. Simple, readable code is often better than pattern-heavy code. Ask yourself: β€œWould this be simpler without the pattern?”

πŸ”— Continue Learning