Skip to main content
Software Engineering Design Patterns

Low Level Design (LLD)

A complete LLD guide: UML class diagrams, all major GoF design patterns with working code, and full interview walkthroughs for Parking Lot, Elevator, Library, Vending Machine, and ATM.

12 sections · 35 min read

What is Low Level Design

Low Level Design (LLD) is the process of defining the internal structure of a specific component or module — the classes, interfaces, their attributes and methods, and how they interact via design patterns. LLD answers: "How do we implement this feature in code?"

LLD vs HLD: High Level Design (HLD) defines the system architecture — which services exist, how they communicate, what databases they use, how they scale. LLD zooms in on one service or feature and answers how the code inside is structured. HLD is for the system; LLD is for the module.

What interviewers assess in an LLD round: - Can you identify the right entities (classes/interfaces) from requirements? - Do you understand object relationships (IS-A, HAS-A, association)? - Can you apply design patterns appropriately — not cargo-culted, but purposefully? - Is your design extensible without modification? - Do you handle edge cases (concurrency, invalid input, lifecycle)?

Typical LLD interview questions: Design a Parking Lot, Elevator System, Library Management System, Chess Game, Vending Machine, ATM, Online Auction, Movie Ticket Booking.

UML Class Diagrams

A UML class diagram is the standard way to communicate LLD. Each class is drawn as a box with three compartments: class name (top), attributes (middle), methods (bottom). Visibility prefixes: + public, - private, # protected.

Relationship arrows: - Inheritance (IS-A): solid line with a hollow triangle arrowhead pointing to the parent - Implementation (IS-A with interface): dashed line with hollow triangle to the interface - Composition (strong HAS-A): solid line with a filled diamond at the "whole" end - Aggregation (weak HAS-A): solid line with a hollow diamond at the "whole" end - Association: plain arrow (knows about, uses) - Dependency (uses temporarily): dashed arrow

Multiplicity notation: 1 (exactly one), * (zero or more), 0..1 (optional), 1..* (one or more). Place multiplicity at both ends of the relationship line.

For interviews, you don't need pixel-perfect UML. What matters: clearly name every class and interface, show key attributes (type + name), show public methods, and draw the right relationship type with multiplicity. Interviewers care about the design, not the syntax.

# Quick UML → code translation

# Interface (abstract base class)
class Vehicle:
    def start(self): raise NotImplementedError
    def stop(self): raise NotImplementedError

# Inheritance (IS-A): Car ──▷ Vehicle
class Car(Vehicle):
    brand: str
    model: str
    def start(self): ...
    def stop(self): ...

# Composition (filled diamond): Car ◆──── Engine
# Car creates and owns Engine; Engine cannot exist without Car
class Engine:
    horsepower: int
    fuel_type: str
    def ignite(self): ...

class Car(Vehicle):
    def __init__(self, brand, hp):
        self.brand = brand
        self._engine = Engine(hp, "petrol")  # owns it

# Aggregation (hollow diamond): Garage ◇──── Car
# Garage holds Cars but doesn't own their lifecycle
class Garage:
    def __init__(self):
        self.cars = []         # cars exist independently
    def park(self, car: Car):
        self.cars.append(car)

Creational Design Patterns

Creational patterns abstract the object creation process, making systems independent of how their objects are created, composed, and represented.

Singleton: ensures a class has only one instance and provides a global access point. Use for: configuration managers, thread pools, loggers, connection pools. Thread-safe in Python using a lock or module-level initialization.

Factory Method: defines an interface for creating an object but lets subclasses decide which class to instantiate. Use when you want to defer instantiation to subclasses. The creator doesn't know which concrete class will be instantiated.

Abstract Factory: creates families of related objects without specifying their concrete classes. Use when your system needs to be independent of how its products are created and when it needs to work with families of related objects (Windows UI vs Mac UI).

Builder: separates the construction of a complex object from its representation. Use when: an object requires many optional parameters (avoid the telescoping constructor anti-pattern), the construction process must allow different representations, you want step-by-step construction with validation.

Prototype: creates new objects by cloning an existing one. Use when object creation is expensive and a similar object already exists, or when classes to instantiate are specified at runtime.

import threading

# SINGLETON — thread-safe
class DatabaseConnection:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:   # double-checked locking
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if not self._initialized:
            self.connection = "Connected to DB"
            self._initialized = True

# FACTORY METHOD
class Notification:
    def send(self, message): raise NotImplementedError

class EmailNotification(Notification):
    def send(self, msg): return f"Email: {msg}"

class SMSNotification(Notification):
    def send(self, msg): return f"SMS: {msg}"

class PushNotification(Notification):
    def send(self, msg): return f"Push: {msg}"

def notification_factory(channel: str) -> Notification:
    mapping = {"email": EmailNotification, "sms": SMSNotification, "push": PushNotification}
    cls = mapping.get(channel)
    if not cls: raise ValueError(f"Unknown channel: {channel}")
    return cls()

# BUILDER
class QueryBuilder:
    def __init__(self): self._table = ""; self._conditions = []; self._limit = None

    def from_table(self, table):
        self._table = table; return self

    def where(self, condition):
        self._conditions.append(condition); return self

    def limit(self, n):
        self._limit = n; return self

    def build(self):
        sql = f"SELECT * FROM {self._table}"
        if self._conditions:
            sql += " WHERE " + " AND ".join(self._conditions)
        if self._limit:
            sql += f" LIMIT {self._limit}"
        return sql

q = QueryBuilder().from_table("users").where("active=1").where("age>18").limit(10).build()
print(q)  # SELECT * FROM users WHERE active=1 AND age>18 LIMIT 10

Structural Design Patterns

Structural patterns deal with object composition — how to assemble objects and classes into larger structures while keeping those structures flexible and efficient.

Adapter: converts the interface of a class into another interface the client expects. Use when integrating legacy code or third-party libraries with incompatible interfaces. The adapter wraps the incompatible class and translates calls.

Decorator: adds behavior to an object dynamically without changing its class. Use when you want to add responsibilities to objects without subclassing. Python's @functools.lru_cache is a decorator. Coffee with milk, sugar, cream — each is a decorator adding cost and description.

Facade: provides a simplified interface to a complex subsystem. Use when a system is very complex and you want to provide a simpler interface for common tasks. A HomeTheaterFacade with watchMovie() hides DVD player, projector, amplifier, and lights setup.

Proxy: provides a surrogate or placeholder that controls access to another object. Types: virtual proxy (lazy loading), protection proxy (access control), remote proxy (local representative for remote object), caching proxy.

Composite: composes objects into tree structures to represent part-whole hierarchies. Let clients treat individual objects and compositions uniformly. A file system: File and Directory both implement FileSystemItem. A Directory contains FileSystemItems (which can be Files or other Directories).

Flyweight: uses sharing to support large numbers of fine-grained objects efficiently. Share common state (intrinsic) among many objects; keep unique state (extrinsic) outside. Example: a text editor sharing character format objects instead of creating one per character.

# ADAPTER — make incompatible interfaces work together
class EuropeanSocket:
    def voltage(self): return 220
    def live(self): return "L"
    def neutral(self): return "N"

class USPlug:
    def voltage(self): return 110
    def hot(self): return "H"
    def neutral(self): return "N"

class EuropeanToUSAdapter(USPlug):
    def __init__(self, socket: EuropeanSocket):
        self._socket = socket
    def voltage(self): return self._socket.voltage() // 2  # simplified
    def hot(self): return self._socket.live()

# DECORATOR — add behavior without subclassing
class Coffee:
    def cost(self): return 2.0
    def description(self): return "Plain coffee"

class MilkDecorator:
    def __init__(self, coffee): self._coffee = coffee
    def cost(self): return self._coffee.cost() + 0.5
    def description(self): return self._coffee.description() + ", milk"

class SugarDecorator:
    def __init__(self, coffee): self._coffee = coffee
    def cost(self): return self._coffee.cost() + 0.25
    def description(self): return self._coffee.description() + ", sugar"

c = Coffee()
c = MilkDecorator(c)
c = SugarDecorator(c)
print(c.description(), c.cost())  # Plain coffee, milk, sugar 2.75

# FACADE
class HomeTheaterFacade:
    def __init__(self, dvd, projector, amplifier, lights):
        self.dvd = dvd; self.proj = projector
        self.amp = amplifier; self.lights = lights

    def watch_movie(self, movie):
        self.lights.dim(10)
        self.proj.on(); self.proj.widescreen()
        self.amp.on(); self.amp.set_volume(5)
        self.dvd.on(); self.dvd.play(movie)

Behavioral Design Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.

Observer: defines a one-to-many dependency so when one object changes state, all its dependents are notified automatically. Use for event systems, UI frameworks (MVC), stock tickers. Subject maintains a list of observers; all observers implement an update() interface.

Strategy: defines a family of algorithms, encapsulates each, and makes them interchangeable. Lets the algorithm vary independently from the clients that use it. Sorting strategies, payment methods, compression algorithms.

Command: encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Remote controls, transaction systems, undo/redo.

State: allows an object to alter its behavior when its internal state changes. Appears as if the object changed its class. Vending machine states (idle, has_coin, dispensing), order states (placed, paid, shipped, delivered).

Template Method: defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. Subclasses can override steps without changing the algorithm's structure. Data parsing pipelines, test frameworks.

Iterator: provides a sequential way to access elements of a collection without exposing its internal representation. Every Python for loop uses the iterator protocol (__iter__ and __next__).

Chain of Responsibility: passes a request along a chain of handlers. Each handler decides to process or pass it on. HTTP middleware, logging handlers, approval workflows.

# OBSERVER
class EventEmitter:
    def __init__(self):
        self._listeners = {}

    def on(self, event, listener):
        self._listeners.setdefault(event, []).append(listener)

    def emit(self, event, *args):
        for listener in self._listeners.get(event, []):
            listener(*args)

emitter = EventEmitter()
emitter.on("data", lambda d: print(f"Handler 1: {d}"))
emitter.on("data", lambda d: print(f"Handler 2: {d}"))
emitter.emit("data", {"user": "Alice"})

# STRATEGY
class Sorter:
    def __init__(self, strategy):
        self._strategy = strategy

    def sort(self, data):
        return self._strategy(data)

quick  = Sorter(sorted)
bubble = Sorter(lambda d: sorted(d, reverse=True))

# STATE — Vending Machine
class VendingMachine:
    def __init__(self):
        self.state = "idle"
        self.balance = 0

    def insert_coin(self, amount):
        if self.state != "idle": return
        self.balance += amount
        self.state = "has_coin"
        print(f"Inserted {amount}. Balance: {self.balance}")

    def select_item(self, price):
        if self.state != "has_coin": print("Insert coin first"); return
        if self.balance < price: print("Insufficient balance"); return
        self.balance -= price
        self.state = "dispensing"
        print("Dispensing item...")
        self.state = "idle"

    def cancel(self):
        if self.state == "has_coin":
            print(f"Returning {self.balance}")
            self.balance = 0; self.state = "idle"

LLD Interview: Design a Parking Lot

Requirements clarification: How many floors? Multiple vehicle types (motorcycle, car, truck)? Pricing model (flat rate, hourly)? Entry/exit points? Need to track availability in real time?

Core entities: ParkingLot, ParkingFloor, ParkingSpot (types: COMPACT, LARGE, MOTORCYCLE), Vehicle (subclasses: Car, Truck, Motorcycle), Ticket (assigned on entry), ParkingAttendant, PaymentService.

Class relationships: ParkingLot HAS-A list of ParkingFloors. Each ParkingFloor HAS-A list of ParkingSpots. A ParkingSpot HAS-A optional Vehicle (when occupied). A Ticket HAS-A Vehicle, a ParkingSpot, and an entry timestamp.

Key methods: ParkingLot.find_spot(vehicle_type) → ParkingSpot, ParkingLot.park(vehicle) → Ticket, ParkingLot.exit(ticket) → Payment, ParkingSpot.is_available() → bool, PaymentService.calculate_fee(ticket) → amount.

Design decisions: Use Strategy pattern for pricing (HourlyPricing, FlatRatePricing). Use Observer to update a display board when spot availability changes. Spot assignment: iterate floors top-to-bottom to preserve ground-floor spots for handicapped; or use a priority queue keyed by floor number.

Edge cases: What if all spots are full? What happens if a ticket is lost? How do you handle a vehicle larger than its assigned spot class? Concurrent entry from multiple entry points — thread-safe spot assignment needed.

from enum import Enum
from datetime import datetime

class SpotType(Enum):
    MOTORCYCLE = 1
    COMPACT = 2
    LARGE = 3

class VehicleType(Enum):
    MOTORCYCLE = 1
    CAR = 2
    TRUCK = 3

VEHICLE_TO_SPOT = {
    VehicleType.MOTORCYCLE: SpotType.MOTORCYCLE,
    VehicleType.CAR: SpotType.COMPACT,
    VehicleType.TRUCK: SpotType.LARGE,
}

class ParkingSpot:
    def __init__(self, spot_id, spot_type: SpotType):
        self.id = spot_id
        self.type = spot_type
        self.vehicle = None

    def is_available(self): return self.vehicle is None

    def assign(self, vehicle): self.vehicle = vehicle

    def free(self): self.vehicle = None

class Ticket:
    def __init__(self, vehicle, spot):
        self.vehicle = vehicle
        self.spot = spot
        self.entry_time = datetime.now()

class ParkingLot:
    def __init__(self):
        self._spots = {t: [] for t in SpotType}

    def add_spot(self, spot: ParkingSpot):
        self._spots[spot.type].append(spot)

    def park(self, vehicle_type: VehicleType, vehicle) -> Ticket:
        spot_type = VEHICLE_TO_SPOT[vehicle_type]
        for spot in self._spots[spot_type]:
            if spot.is_available():
                spot.assign(vehicle)
                return Ticket(vehicle, spot)
        raise Exception("No available spots")

    def exit(self, ticket: Ticket) -> float:
        duration = (datetime.now() - ticket.entry_time).seconds / 3600
        ticket.spot.free()
        return max(1.0, duration * 2.0)   # $2/hour, minimum $1

LLD Interview: Design an Elevator System

Requirements: N elevators, M floors. Each elevator has a direction (UP, DOWN, IDLE) and a current floor. Passengers press external hall buttons (floor + direction) and internal cabin buttons (destination floor). Goal: minimize wait time.

Core entities: Building, Elevator, ElevatorController, Request (HallCall or CabinCall), Door.

Elevator states: IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN. Use the State pattern to manage transitions cleanly.

Scheduling algorithm — SCAN (elevator algorithm): the elevator moves in one direction servicing all requests on the way, then reverses. Variants: LOOK (reverses when no more requests in current direction), SSTF (shortest seek time first — may starve distant requests), FCFS (simple but inefficient).

ElevatorController responsibilities: receive hall calls, assign the best elevator (by proximity + direction compatibility), maintain a sorted set of pending floors for each elevator, emit movement commands.

Key methods: ElevatorController.request(floor, direction) → assigns to best elevator. Elevator.add_destination(floor). Elevator.move() — advances one floor per tick. Elevator.open_door() / close_door().

Edge cases: elevator at capacity (weight sensor), emergency stop, door obstruction sensor, fire alarm (send all to ground floor).

from enum import Enum

class Direction(Enum):
    UP = 1; DOWN = -1; IDLE = 0

class Elevator:
    def __init__(self, elevator_id, total_floors):
        self.id = elevator_id
        self.current_floor = 0
        self.direction = Direction.IDLE
        self.destinations = set()
        self.total_floors = total_floors

    def add_destination(self, floor: int):
        self.destinations.add(floor)
        self._update_direction()

    def _update_direction(self):
        if not self.destinations:
            self.direction = Direction.IDLE
        elif max(self.destinations) > self.current_floor:
            self.direction = Direction.UP
        else:
            self.direction = Direction.DOWN

    def step(self):
        if self.direction == Direction.IDLE:
            return
        self.current_floor += self.direction.value
        if self.current_floor in self.destinations:
            self.destinations.remove(self.current_floor)
            print(f"Elevator {self.id}: doors open at floor {self.current_floor}")
        self._update_direction()

class ElevatorController:
    def __init__(self, num_elevators, total_floors):
        self.elevators = [Elevator(i, total_floors) for i in range(num_elevators)]

    def request(self, floor: int, direction: Direction):
        best = min(self.elevators, key=lambda e: abs(e.current_floor - floor))
        best.add_destination(floor)
        return best.id

LLD Interview: Design a Library Management System

Requirements: catalog management (books, magazines), member registration, book search, borrow and return, reservations, fine calculation, librarian vs member roles.

Core entities: Library, Book, BookItem (physical copy — a Book can have many BookItems), Member, Librarian, BorrowRecord, Reservation, Fine, Catalog.

A Book is the abstract record (ISBN, title, author, genre). A BookItem is a physical instance (barcode, shelf location, condition). One Book can have 3 copies = 3 BookItems.

Key relationships: Catalog HAS-A list of Books. Library HAS-A Catalog, list of Members, list of BorrowRecords. Member HAS-A list of active BorrowRecords (max 5). BorrowRecord HAS-A BookItem + Member + due_date.

Key methods: Catalog.search(query) → List[Book], Library.borrow(member, book_item) → BorrowRecord, Library.return_book(borrow_record) → Fine or None, Member.reserve(book) → Reservation, Fine.calculate(due_date, return_date) → amount.

Design patterns: Use Observer to notify waiting members when a book becomes available (reservation queue). Use Strategy for fine calculation (different policies: $0.25/day, waived for first offence). Use Factory for creating different account types (Member vs Librarian).

from datetime import datetime, timedelta
from enum import Enum

class BookStatus(Enum):
    AVAILABLE = "available"
    BORROWED = "borrowed"
    RESERVED = "reserved"

class Book:
    def __init__(self, isbn, title, author):
        self.isbn = isbn; self.title = title; self.author = author
        self.copies = []

    def add_copy(self, copy): self.copies.append(copy)

    def available_copies(self):
        return [c for c in self.copies if c.status == BookStatus.AVAILABLE]

class BookCopy:
    def __init__(self, barcode, book: Book):
        self.barcode = barcode
        self.book = book
        self.status = BookStatus.AVAILABLE

class Member:
    MAX_BOOKS = 5

    def __init__(self, member_id, name):
        self.id = member_id; self.name = name
        self.active_borrows = []

    def can_borrow(self): return len(self.active_borrows) < self.MAX_BOOKS

class BorrowRecord:
    LOAN_DAYS = 14
    FINE_PER_DAY = 0.25

    def __init__(self, copy: BookCopy, member: Member):
        self.copy = copy; self.member = member
        self.borrow_date = datetime.now()
        self.due_date = self.borrow_date + timedelta(days=self.LOAN_DAYS)
        self.return_date = None

    def fine(self) -> float:
        if not self.return_date or self.return_date <= self.due_date:
            return 0.0
        overdue = (self.return_date - self.due_date).days
        return overdue * self.FINE_PER_DAY

class Library:
    def __init__(self):
        self.catalog = {}   # isbn → Book

    def add_book(self, book: Book): self.catalog[book.isbn] = book

    def borrow(self, member: Member, isbn: str) -> BorrowRecord:
        if not member.can_borrow(): raise Exception("Borrow limit reached")
        book = self.catalog.get(isbn)
        if not book: raise Exception("Book not found")
        copies = book.available_copies()
        if not copies: raise Exception("No copies available")
        copy = copies[0]
        copy.status = BookStatus.BORROWED
        record = BorrowRecord(copy, member)
        member.active_borrows.append(record)
        return record

LLD Interview: Design a Vending Machine

Requirements: Multiple products at different prices, coin/bill insertion, product selection, change dispensal, refund, admin restock, display current balance.

State machine approach (State pattern): VendingMachine transitions through states: IDLE → HAS_MONEY → DISPENSING → CHANGE_RETURN → IDLE. Each state handles inputs differently. Attempting to select a product in IDLE state is a no-op.

Core entities: VendingMachine, Product, Inventory, CoinSlot, Display, StateHandler (Idle, HasMoney, Dispensing).

Key transitions: insert_coin(amount) in IDLE → HAS_MONEY. select_product(id) in HAS_MONEY — if balance >= price → DISPENSING. dispense() → CHANGE_RETURN. return_change() → IDLE. cancel() in any state → returns balance → IDLE.

Inventory management: products indexed by slot ID, each slot has a count. Sold-out products should be greyed out on the display.

Edge cases: exact change required (can the machine make change?), power failure mid-transaction, coin jammed, product stuck in dispenser chute.

from abc import ABC, abstractmethod

class Product:
    def __init__(self, name, price):
        self.name = name; self.price = price

class VendingMachineState(ABC):
    @abstractmethod
    def insert_coin(self, machine, amount): pass
    @abstractmethod
    def select_product(self, machine, slot_id): pass
    @abstractmethod
    def cancel(self, machine): pass

class IdleState(VendingMachineState):
    def insert_coin(self, machine, amount):
        machine.balance += amount
        machine.state = machine.has_money_state
        print(f"Balance: {machine.balance:.2f}")

    def select_product(self, machine, slot_id):
        print("Please insert coins first")

    def cancel(self, machine):
        print("Nothing to cancel")

class HasMoneyState(VendingMachineState):
    def insert_coin(self, machine, amount):
        machine.balance += amount
        print(f"Balance: {machine.balance:.2f}")

    def select_product(self, machine, slot_id):
        product = machine.inventory.get(slot_id)
        if not product: print("Invalid slot"); return
        if machine.balance < product.price:
            print(f"Need {product.price - machine.balance:.2f} more"); return
        machine.balance -= product.price
        print(f"Dispensing {product.name}. Returning {machine.balance:.2f}")
        machine.balance = 0
        machine.state = machine.idle_state

    def cancel(self, machine):
        print(f"Returning {machine.balance:.2f}")
        machine.balance = 0
        machine.state = machine.idle_state

class VendingMachine:
    def __init__(self):
        self.idle_state = IdleState()
        self.has_money_state = HasMoneyState()
        self.state = self.idle_state
        self.balance = 0.0
        self.inventory = {}

    def load_product(self, slot_id, product: Product):
        self.inventory[slot_id] = product

    def insert_coin(self, amount): self.state.insert_coin(self, amount)
    def select(self, slot_id): self.state.select_product(self, slot_id)
    def cancel(self): self.state.cancel(self)

LLD Interview Framework

A repeatable 45-minute LLD interview framework. Interviewers want to see structured thinking, not just code.

Minutes 0–5: Requirements clarification. Ask about: scale (how many users?), supported features (what's in scope?), constraints (any performance SLAs?). Write down the functional requirements you'll design for. This prevents scope creep and shows systematic thinking.

Minutes 5–10: Identify core entities. List nouns in the requirements — these become your classes. Distinguish between entities (things with identity and lifecycle) and value objects (immutable data without identity). Group related attributes.

Minutes 10–20: Define class relationships. Draw the class diagram. Show inheritance (IS-A), composition (filled diamond), and aggregation (hollow diamond). Add multiplicity. Identify interfaces vs abstract classes. This is where most marks are won or lost.

Minutes 20–35: Key methods and design patterns. For each major use case, walk through which methods are called. Identify where patterns add value: Factory for creation, Observer for events, Strategy for algorithms, State for state machines. Write pseudocode for 1–2 core methods.

Minutes 35–45: Edge cases and extensibility. Concurrency (thread-safe methods?). Error handling (what if the DB is down?). Extensibility (what if we add a new vehicle type? a new payment method?). How would you test this? What metrics would you monitor?

Common LLD Interview Questions

These are the top 20 LLD questions and the key design points for each.

Design a Parking Lot — entities: ParkingSpot (type), Vehicle (type), Ticket, ParkingFloor. Patterns: Strategy (pricing), Observer (display board).

Design an Elevator System — entities: Elevator, Request, ElevatorController. Patterns: State (elevator state), Strategy (scheduling algorithm).

Design a Library Management System — entities: Book, BookCopy, Member, BorrowRecord. Patterns: Observer (notify on return), Strategy (fine calculation).

Design a Vending Machine — entities: Product, Inventory, CoinSlot. Patterns: State (idle/has_money/dispensing).

Design an ATM — entities: Card, Account, Transaction, CashDispenser. Patterns: State (idle/has_card/authenticated/transaction), Strategy (auth method).

Design a Chess Game — entities: Board, Piece (subclasses: King, Queen, Rook, Bishop, Knight, Pawn), Player, Move. Patterns: Strategy (piece movement rules).

Design Online Auction — entities: Item, Bid, Auction, Bidder, AuctionNotifier. Patterns: Observer (notify bidders on new bid), Strategy (auction type: English, Dutch).

Design Movie Ticket Booking — entities: Movie, Show, Seat, Booking, Payment. Patterns: Singleton (seat availability cache), Strategy (pricing: normal, premium).

Design a Hotel Booking System — entities: Hotel, Room (type), Booking, Guest. Patterns: Builder (complex search query), Strategy (pricing).

Design a Food Delivery System — entities: Restaurant, MenuItem, Order, DeliveryAgent, Customer. Patterns: Observer (order status updates), Strategy (delivery assignment).

Design a Social Network (basic) — entities: User, Post, Comment, Like, Follow. Patterns: Observer (news feed updates), Iterator (paginated feed).

Design a Ride-Sharing App — entities: Driver, Rider, Trip, Location, PricingEngine. Patterns: Strategy (pricing: surge, flat), Observer (location updates).

Continue Learning

Ready to test your knowledge?

Apply what you learned with curated practice problems.

Find this useful?

This guide is completely free. If it helped, consider buying me a coffee — it keeps new content coming.

Support on Ko-fi
Buy me a coffee