Courses › Low-Level Design › Foundations

Design Patterns

The handful of patterns that actually show up: Strategy, Factory, Observer, State and more.

There are 23 classic Gang of Four patterns, but only a handful come up in low-level design interviews. Know these well — when to use them and how to implement them in a few lines — and you'll be well equipped.

Strategy

Use when an algorithm or rule varies and should be swappable: pricing, spot allocation, elevator scheduling, rate-limiting algorithms.

from abc import ABC, abstractmethod

class AllocationStrategy(ABC):
    @abstractmethod
    def find_spot(self, levels, vehicle): ...

class LowestLevelFirst(AllocationStrategy):
    def find_spot(self, levels, vehicle):
        for level in levels:
            spot = level.first_free_spot(vehicle.size)
            if spot:
                return spot
        return None

This is the single most useful pattern in LLD interviews.

State

Use when an object's behavior depends on its state and transitions are well defined: a vending machine (idle → has money → dispensing), an order (created → paid → shipped), an elevator, a traffic light.

Each state is a class implementing the same interface; the context delegates to its current state, and states trigger transitions. This replaces sprawling if state == ... checks in every method.

class VendingState(ABC):
    def insert_money(self, machine, amount): raise InvalidAction()
    def select(self, machine, slot): raise InvalidAction()

class Idle(VendingState):
    def insert_money(self, machine, amount):
        machine.balance += amount
        machine.state = HasMoney()

class HasMoney(VendingState):
    def insert_money(self, machine, amount):
        machine.balance += amount
    def select(self, machine, slot):
        machine.dispense(slot)
        machine.state = Idle()

Observer

Use when several components need to react to an event without the source knowing about them: display boards updated when spots change, notifications when an order ships.

class EventBus:
    def __init__(self): self.subscribers = {}
    def subscribe(self, event, handler):
        self.subscribers.setdefault(event, []).append(handler)
    def publish(self, event, payload):
        for handler in self.subscribers.get(event, []):
            handler(payload)

Factory

Use when object creation involves logic or choosing between types: creating the right Vehicle from input, building a rate limiter from configuration. Keeps construction logic in one place.

Singleton

Use when exactly one instance must exist — but use it sparingly. It's essentially global state and makes testing harder. In interviews, prefer creating one instance and injecting it; mention singleton only if the problem explicitly calls for it.

Decorator

Use when adding responsibilities to an object dynamically: adding toppings to a coffee order, adding logging or caching around a service. Each decorator wraps the same interface.

Builder

Use when constructing objects with many optional parameters, to avoid huge constructors.

Command

Use when you need to queue, log or undo operations: a text editor's undo stack, elevator requests as objects, job queues.

Choosing patterns in an interview

[!WARNING] Patterns are a means, not a goal Using six patterns in a 45-minute interview usually signals over-engineering. Use a pattern when a requirement calls for it, and say which requirement.