??????? Low-Level Design (LLD) Handbook
V1
Low-Level Design UML · OOP Design Patterns
System Design Series

Low-Level Design
Handbook

A comprehensive guide to writing production-ready Low-Level Design documents — covering class design, UML diagrams, API contracts, design patterns, and real system examples for engineering interviews and production systems.

Class Diagrams UML Notation Design Patterns API Contracts SOLID Principles Interview Ready
📐

What is Low-Level Design?

Definition, purpose, and the role LLD plays in software development

Low-Level Design (LLD) is the phase of system design that translates high-level architectural decisions into concrete, implementable blueprints. It specifies exactly how individual components work internally — their classes, methods, attributes, relationships, and interactions — down to the level where a developer can pick it up and write code directly from it.

Think of it this way: HLD answers "what components exist and how do they communicate?" while LLD answers "exactly how is each component built?"

Blueprint for Code

LLD is the spec a developer implements. A well-written LLD document removes ambiguity — developers shouldn't need to infer intent or make structural decisions during implementation.

Communication Tool

LLD documents allow teams to review, critique, and align on structure before code is written — when changes are cheap. They're also invaluable for onboarding new team members.

Interview Standard

LLD is a core component of software engineering interviews at top companies. It tests object-oriented thinking, design pattern knowledge, and the ability to model real-world systems.

💡
LLD is not pseudocode. It defines the structure (classes, interfaces, relationships, contracts) not the step-by-step algorithmic logic of every method. Implementation details like loop bodies belong in the code itself, not the LLD document.
⚖️

HLD vs LLD

High-Level Design and Low-Level Design — what's the difference?

These two phases of system design are complementary and sequential. HLD is always done first — you can't design internals before you've decided what components exist.

Dimension High-Level Design (HLD) Low-Level Design (LLD)
Focus System architecture, component topology, data flow between services Class structure, method signatures, object relationships, algorithms
Audience Architects, product managers, senior engineers, stakeholders Developers who will implement the component
Diagrams used Architecture diagrams, ER diagrams, deployment diagrams, data flow diagrams Class diagrams, sequence diagrams, state diagrams, activity diagrams
Granularity Services, databases, message queues, external APIs, load balancers Classes, interfaces, enums, methods, attributes, design patterns
Technology choices Which database? Which cloud? Which language? Which design pattern? Which data structure? Which abstraction?
Scalability focus Horizontal scaling, caching layers, load balancing, replication Extensibility, maintainability, correctness, single-component performance
Output Architecture diagram, capacity estimates, technology stack decisions Class diagrams, sequence diagrams, API contracts, data models
Example question "Design Twitter" — focus on system scale "Design a Parking Lot system" — focus on OOP modeling
In practice, HLD and LLD coexist. A large system like Netflix has an HLD covering microservices and CDN topology, and a separate LLD for each service (e.g., the Recommendation Service has its own class diagram, state machine for user preference updates, and API contracts). Never combine them into one doc.

What HLD covers that LLD does not

HLD-only topics
  • CAP theorem trade-offs and consistency model
  • Service discovery and inter-service communication (gRPC, REST, events)
  • Database sharding and replication strategy
  • CDN, caching layers (Redis, Memcached)
  • Load balancer configuration
  • Deployment topology (region, availability zones)
  • Capacity planning and throughput estimates
LLD-only topics
  • Class hierarchy and interface definitions
  • Method signatures and return types
  • Object lifecycle (creation, destruction, pooling)
  • Design patterns applied within a component
  • State machine for a specific entity
  • Thread safety and synchronization within a class
  • Error handling and exception hierarchy
📅

When to Write LLD

Situations where LLD adds value — and when it's overkill
Write LLD when…
  • A feature is complex enough that two developers would implement it differently
  • Multiple developers need to work on the same component
  • The component will be used as a library or SDK by other teams
  • The domain logic is non-trivial (billing, inventory, scheduling)
  • You are designing for an engineering interview
  • You need a reviewable artifact before starting a sprint
  • A component needs to be extensible without breaking existing users
Skip (or simplify) LLD when…
  • The change is a bug fix or small configuration tweak
  • You're building a quick prototype or spike that won't ship
  • The component is genuinely trivial (a single CRUD controller)
  • You're working alone on a short-lived script or one-off tool
  • A well-known framework already prescribes the structure (Django, Rails)
📋

What to Include in an LLD Document

The six sections every solid LLD should contain

A complete LLD document contains all information a developer needs to implement the component correctly — no more, no less. The standard sections are:

#SectionWhat it coversRequired?
1 Requirements & Constraints Functional requirements (what the system must do), non-functional requirements (performance, scalability, thread safety), assumptions, and out-of-scope items MUST
2 Core Entities & Enumerations Domain objects, value objects, enums, and their attributes. This is the vocabulary of the system — defined before anything else. MUST
3 Class Diagram All classes, interfaces, abstract classes, their attributes, methods, and relationships (inheritance, composition, association, dependency) MUST
4 Sequence Diagrams Interaction flows for key use cases — showing which objects call which methods and in what order. Typically 2–4 flows covering the happy path and primary edge cases. MUST
5 API / Interface Contracts Public method signatures with parameter types, return types, and exceptions thrown. This is the contract between components. MUST
6 Data Model Database schema or in-memory data structures, with field types, constraints, and indices. May include a state machine diagram if entities have lifecycle states. SHOULD
7 Design Patterns Used Explicit callout of which patterns are applied and why — helps reviewers understand intent, not just structure. SHOULD
8 Error Handling Strategy Exception hierarchy, error codes, retry logic, and graceful degradation paths. SHOULD
9 Concurrency & Thread Safety Which classes need to be thread-safe, what locking mechanisms are used, and which state is shared vs. local. IF NEEDED
10 Extensibility Notes How to add a new payment provider, a new vehicle type, a new sort algorithm — the extension points and how to use them. IF NEEDED
🧩

Class Design Principles

How to model the right classes, attributes, and methods

Step 1 — Identify entities from requirements

Read the requirements and underline the nouns — these become candidate classes. Underline the verbs — these become candidate methods. Filter out nouns that are attributes of another class vs. ones that deserve their own class.

example — parking lot requirements
// REQUIREMENT: "Drivers park vehicles in spots. Spots have types. // Parking lots charge based on time. Tickets are issued on entry." // NOUNS (candidate classes): // Driver, Vehicle, Spot, ParkingLot, Ticket, Payment, Rate // VERBS (candidate methods): // park(), exit(), issueTicket(), calculateFee(), processPayment() // ATTRIBUTES (belong inside a class, not standalone classes): // spotNumber, entryTime, licensePlate, vehicleType

Step 2 — Classify each entity

Concrete class

Has state and behavior. Can be instantiated. Use for real-world objects: Car, ParkingSpot, Ticket.

Abstract class

Defines a base with shared state, but some behavior is deferred. Use when subclasses share data but specialize behavior: Vehicle with Car, Truck, Motorcycle.

Interface

Defines a contract — no state. Use for capabilities that cut across hierarchies: Payable, Notifiable, Serializable. Prefer interfaces over abstract classes.

Enum

Fixed set of named constants. Use for states, types, and categories: VehicleType, SpotStatus, PaymentMethod.

Service / Manager

Orchestrates behavior but holds little state. Use for use-case logic: ParkingLotManager, BillingService, NotificationService.

Value Object

Immutable, equality by value not identity. Use for concepts with no identity of their own: Money, Address, DateRange.

Step 3 — Define relationships

RelationshipMeaningUMLExample
Inheritance "is-a" — subclass extends superclass ——▷ (solid + hollow arrow) Car extends Vehicle
Interface implementation "implements" — class fulfills a contract - - ▷ (dashed + hollow arrow) CreditCardPayment implements IPayable
Composition "owns" — child cannot exist without parent ◆—— (filled diamond) ParkingLot owns ParkingSpots
Aggregation "has-a" — child can exist without parent ◇—— (hollow diamond) Order aggregates Products
Association "uses" — objects collaborate, neither owns the other ——→ (solid arrow) Driver associates with Vehicle
Dependency "depends on" — uses temporarily (method param) - - → (dashed arrow) BillingService depends on RateCalculator
⚠️
Composition vs. Inheritance trap: Don't inherit just to reuse code. Inherit only when a true "is-a" relationship exists. If you're inheriting to share a method, use composition instead. Rule of thumb: if you can say "Car IS-A Vehicle" naturally, inherit. If it's "LoggedUserService IS-A LoggingService" — that's wrong, compose instead.
📊

UML Diagrams in LLD

Class diagrams, sequence diagrams, state machines — when and how to use them

Class Diagram notation

UML Class Box Anatomy
┌──────────────────────────────────┐ │ <<interface>> │ ← stereotype │ IPayable │ ← class name ├──────────────────────────────────┤ │ - amount: decimal │ ← attributes │ - currency: string │ - private │ # createdAt: DateTime │ # protected │ + id: Guid │ + public ├──────────────────────────────────┤ │ + calculateFee(): decimal │ ← methods │ + processPayment(): bool │ (name: type) │ # validate(): void │ └──────────────────────────────────┘

Visibility and stereotypes

Visibility modifiers
  • + public — accessible from anywhere
  • - private — accessible only within the class
  • # protected — accessible within class and subclasses
  • ~ package — accessible within the same package
Common stereotypes
  • <<interface>> — defines a contract
  • <<abstract>> — cannot be instantiated
  • <<enum>> — fixed set of values
  • <<service>> — stateless orchestrator
  • <<singleton>> — single instance

Sequence Diagram

Sequence diagrams show the flow of messages between objects over time. Use them for the 2–4 most important use cases in your LLD — entry flow, exit flow, payment flow, etc.

sequence diagram — parking entry flow
Driver ParkingLotManager SpotSelector TicketService | | | | |-- enter() ------>| | | | |-- findSpot(type) ->| | | |<-- ParkingSpot ----| | | |-- reserveSpot() -->| | | | |-- status=OCCUPIED | | |-- issueTicket() -------------------- >| | |<------------------------ Ticket ------| |<-- Ticket -------| | | | | | |

State Machine Diagram

Use state diagrams for entities with a lifecycle — Ticket, Order, Booking, ParkingSpot. Every transition should have a trigger and optionally a guard condition and action.

state machine — ParkingSpot
[*] ──────────────────────────────────── AVAILABLE │ reserve() │ (vehicle type │ matches) ▼ RESERVED │ vehicleParked()│ ▼ OCCUPIED ──── vehicleExited() ──── AVAILABLE │ maintenance() │ ▼ MAINTENANCE ─── repairComplete() ─── AVAILABLE
In interviews, draw diagrams incrementally. Start with the class diagram skeleton (class names + relationships), then add attributes, then add methods. Interviewers care more about seeing your thinking process than a complete diagram upfront.
🔌

API & Interface Contracts

Defining the public surface of each component

Interface contracts are the agreements between components. They specify what operations are available, what inputs they accept, what they return, and what exceptions they throw. Write these before implementation — they force you to think from the consumer's perspective.

java — interface definition example
// ── Core interface: all rate calculators must implement this ── public interface RateCalculator { /** * Calculates the parking fee. * @param spot The parking spot used * @param duration Duration of stay in minutes * @return Total fee in the lot's base currency (pence/cents) * @throws IllegalArgumentException if duration is negative */ long calculateFee(ParkingSpot spot, long durationMinutes); } // ── Service contract ── public interface ParkingLotService { Ticket checkIn(Vehicle vehicle) throws NoAvailableSpotException; Receipt checkOut(String ticketId) throws InvalidTicketException; boolean hasAvailability(VehicleType type); SpotStatus getSpotStatus(int spotId) throws SpotNotFoundException; } // ── Custom exception hierarchy ── public class ParkingException extends RuntimeException { ... } public class NoAvailableSpotException extends ParkingException { ... } public class InvalidTicketException extends ParkingException { ... } public class SpotNotFoundException extends ParkingException { ... }
📌
Exception hierarchy matters. Define a root exception for your module (e.g., ParkingException). All module-specific exceptions extend it. This lets callers catch broadly (catch ParkingException) or specifically (catch NoAvailableSpotException) — and prevents leaking unrelated runtime exceptions from dependencies.
🗄️

Data Models

Database schema design and in-memory data structures

The data model section defines how entities are persisted or stored in memory. Include table definitions for relational systems, collection structures for in-memory systems, and any indices critical for performance.

sql — parking lot schema
-- Vehicles CREATE TABLE vehicles ( id UUID PRIMARY KEY, license_plate VARCHAR(20) NOT NULL UNIQUE, type VARCHAR(20) NOT NULL, -- CAR, TRUCK, MOTORCYCLE, etc. owner_id UUID REFERENCES users(id) ); -- Parking Spots CREATE TABLE parking_spots ( id INT PRIMARY KEY, floor INT NOT NULL, spot_number INT NOT NULL, type VARCHAR(20) NOT NULL, -- COMPACT, LARGE, HANDICAPPED, EV status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE', UNIQUE (floor, spot_number) ); -- Tickets CREATE TABLE tickets ( id UUID PRIMARY KEY, vehicle_id UUID REFERENCES vehicles(id), spot_id INT REFERENCES parking_spots(id), entry_time TIMESTAMPTZ NOT NULL, exit_time TIMESTAMPTZ, status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' -- ACTIVE, PAID, EXPIRED ); -- Payments CREATE TABLE payments ( id UUID PRIMARY KEY, ticket_id UUID REFERENCES tickets(id), amount BIGINT NOT NULL, -- stored in pence/cents method VARCHAR(20) NOT NULL, -- CASH, CARD, EWALLET paid_at TIMESTAMPTZ NOT NULL ); -- Key indices for performance CREATE INDEX idx_spots_status_type ON parking_spots(status, type); CREATE INDEX idx_tickets_vehicle ON tickets(vehicle_id, status);

In-memory data structure decisions

Use caseStructureWhy
Find available spot by type quicklyMap<VehicleType, Queue<ParkingSpot>>O(1) lookup by type, O(1) pop of next available spot
Look up ticket by IDConcurrentHashMap<String, Ticket>Thread-safe O(1) lookup for concurrent checkouts
Track all spots on a floorMap<Integer, List<ParkingSpot>>O(1) floor lookup, ordered list for display
Audit log of all eventsLinkedList<ParkingEvent>Append-only, ordered insertion is O(1)
🔄

How to Create an LLD — Step by Step

The process from requirements to reviewable document
Step 1 — Clarify Requirements (5 min in interviews)

Before modeling anything, clarify scope. Ask: What are the core use cases? What can we ignore? What are the constraints (scale, concurrency, persistence)?

  • List 4–6 functional requirements explicitly
  • List 2–3 non-functional requirements (thread safety, persistence, performance)
  • Call out what is out of scope
Step 2 — Identify Core Entities

Extract nouns from requirements. For each noun, decide: is it a class, an attribute, or an enum? Group related attributes together — don't create a class for every noun.

Step 3 — Define Enumerations and Value Objects

Nail down the vocabulary before drawing classes. Define all enums (VehicleType, SpotStatus, PaymentMethod) and value objects (Money, Duration) first — these don't change and anchor everything else.

Step 4 — Draw the Class Diagram

Start with class names and relationships only. Then add attributes. Then add methods. Don't try to do all three at once — you'll miss relationships. Verify every relationship type (inheritance vs. composition vs. association).

Step 5 — Define Key Interfaces and Contracts

Write out the public API for each service/manager class. This forces you to think about what callers need, not what implementers want to expose.

Step 6 — Draw 2–3 Sequence Diagrams

Choose the most important flows: the happy path for the primary use case, and one or two edge cases. This validates that your class design can actually support the required behavior.

Step 7 — Apply and Annotate Design Patterns

Review your design and call out which patterns you've used (or should use). Patterns aren't decoration — they communicate standard solutions to standard problems. If you're using Strategy, Singleton, or Observer, say so explicitly and explain why.

Step 8 — Review Against SOLID

Walk through each principle. SRP: does each class have one reason to change? OCP: can you add a new vehicle type without editing existing classes? LSP: does every subclass honor the superclass contract? ISP: are your interfaces narrow? DIP: do high-level classes depend on abstractions?

♟️

Design Patterns in LLD

The most commonly applied patterns and when to use them
PatternCategoryWhen to Use in LLDExample
Singleton Creational Exactly one instance of a resource manager — use carefully, prefer DI ParkingLotManager — one lot, one manager instance
Factory Method Creational Creating objects without specifying the exact class; decouples creation from usage VehicleFactory.create(type) returns Car, Truck, or Motorcycle
Abstract Factory Creational Families of related objects that must be used together ParkingRateFactory that creates rate calculators + receipt formatters
Builder Creational Complex object construction with many optional parameters TicketBuilder for constructing tickets with optional fields
Strategy Behavioral Swappable algorithms or behaviors; avoids conditional chains RateCalculator: HourlyRate, DailyRate, FlatRate strategies
Observer Behavioral Notifying multiple components when state changes, without coupling them Notify display boards and email service when a spot becomes available
State Behavioral An object's behavior changes based on its internal state — replaces large switch/if chains ParkingSpot behavior differs when AVAILABLE vs RESERVED vs OCCUPIED
Command Behavioral Encapsulate operations as objects — enables undo, queuing, logging ReserveSpotCommand, ReleaseSpotCommand — supports undo of reservations
Decorator Structural Adding responsibilities to objects dynamically without subclassing Wrap BaseRateCalculator with WeekendSurchargeDecorator, HolidayDecorator
Facade Structural Simplify a complex subsystem behind a clean interface ParkingLotFacade hides spot selection, ticketing, and payment subsystems
Template Method Behavioral Define the skeleton of an algorithm in a base class, defer steps to subclasses AbstractPaymentProcessor.process() — subclasses implement authorize()
Iterator Behavioral Traverse a collection without exposing its internal structure ParkingFloorIterator walks all spots on a floor sequentially
java — strategy pattern for rate calculation
// Strategy interface public interface RateStrategy { long calculateFee(long durationMinutes, VehicleType type); } // Concrete strategies public class HourlyRate implements RateStrategy { private final long ratePerHour; public long calculateFee(long mins, VehicleType type) { return (mins / 60) * ratePerHour; } } public class FlatDailyRate implements RateStrategy { private final long dailyRate; public long calculateFee(long mins, VehicleType type) { return (mins / 1440 + 1) * dailyRate; // ceil days } } // Context class uses the strategy public class BillingService { private final Map<VehicleType, RateStrategy> strategies; public BillingService(Map<VehicleType, RateStrategy> strategies) { this.strategies = strategies; } public long calculateFee(Ticket ticket) { long duration = computeDurationMinutes(ticket); return strategies .get(ticket.getVehicleType()) .calculateFee(duration, ticket.getVehicleType()); } }
🏗️

SOLID Principles Applied to LLD

Concrete examples of SOLID in a parking lot design
S — Single Responsibility

Each class has exactly one reason to change. Split mixed concerns into focused classes.

✓ DO — focused classes
ParkingSpotSelector // finds spots TicketService // creates tickets BillingService // calculates fees PaymentProcessor // handles payment
✕ DON'T — god class
ParkingLotManager findSpot() issueTicket() calculateFee() processPayment() sendReceipt()
O — Open/Closed

Open for extension, closed for modification. Add new vehicle types or rate strategies without editing existing classes.

// Adding ElectricVehicle — no existing class is modified: public class ElectricVehicle extends Vehicle { private boolean needsCharging; } public class EVRateStrategy implements RateStrategy { ... } // Register in config — done.
L — Liskov Substitution

Every subclass must honor the contract of its superclass. A Motorcycle must be usable anywhere a Vehicle is expected — same behavioral guarantees.

I — Interface Segregation

Don't force implementers to depend on methods they don't use. Split fat interfaces into role-specific ones.

✓ DO — narrow interfaces
interface Readable { findSpot(); } interface Reservable{ reserve(); } interface Releasable{ release(); }
✕ DON'T — bloated interface
interface ISpotRepository { findSpot(); reserve(); release(); update(); delete(); }
D — Dependency Inversion

High-level classes depend on abstractions, not concretions. BillingService should depend on RateStrategy (interface), not HourlyRate (concrete class).

🚗

Full Example: Parking Lot System

Complete LLD from requirements to class diagram

Requirements

Functional Requirements
  • Support multiple vehicle types: car, truck, motorcycle, EV
  • Multiple spot types per vehicle type (compact, large, EV-charging)
  • Issue ticket on entry, collect payment on exit
  • Support multiple floors in the parking lot
  • Multiple payment methods: cash, card, e-wallet
  • Display available spots in real time
Non-Functional Requirements
  • Thread-safe: multiple entry/exit gates concurrently
  • Extensible: new vehicle types must not require existing code changes
  • Spot assignment: O(1) average case
  • Out of scope: online reservations, loyalty programs, ANPR cameras

Core enumerations

java — enums
public enum VehicleType { CAR, TRUCK, MOTORCYCLE, ELECTRIC_VEHICLE } public enum SpotType { COMPACT, LARGE, HANDICAPPED, EV_CHARGING } public enum SpotStatus { AVAILABLE, RESERVED, OCCUPIED, MAINTENANCE } public enum TicketStatus{ ACTIVE, PAID, EXPIRED } public enum PaymentMethod{ CASH, CREDIT_CARD, DEBIT_CARD, EWALLET }

Class diagram (text representation)

uml class diagram
┌─────────────────────────────────────────────────────────────────────────────┐ │ <<abstract>> Vehicle │ │ - id: UUID │ │ - licensePlate: String │ │ + getType(): VehicleType │ └──────────────────────────┬──────────────────────────────────────────────────┘ │ extends ┌────────────────────┼─────────────────────┐ ▼ ▼ ▼ Car {} Truck {} ElectricVehicle { batteryLevel: int } Motorcycle {} ┌───────────────────────────────────┐ ◆ composed of │ ParkingLot │────────────────────► ParkingFloor (1..*) │ - id: int │ │ - address: Address │◆───────────────────► EntryGate (1..*) │ - name: String │ │ + checkIn(Vehicle): Ticket │◆───────────────────► ExitGate (1..*) │ + checkOut(ticketId): Receipt │ │ + getAvailability(): Map │──── uses ──────────► ParkingDisplayBoard └───────────────────────────────────┘ ┌───────────────────────────────────┐ │ ParkingFloor │◆──────────────────► ParkingSpot (1..*) │ - floorNumber: int │ │ + getAvailableSpots(SpotType) │ │ + getSpot(int spotId) │ └───────────────────────────────────┘ ┌───────────────────────────────────┐ │ ParkingSpot │ │ - id: int │ │ - type: SpotType │ │ - status: SpotStatus │ │ - vehicle: Vehicle (nullable) │ │ + isAvailableFor(VehicleType) │ │ + assignVehicle(Vehicle) │ │ + removeVehicle() │ └───────────────────────────────────┘ ┌─────────────────────┐ <<interface>> ┌──────────────────────────────┐ │ RateStrategy │◄── implements ───── │ HourlyRate │ │ + calculateFee() │ │ DailyRate │ └─────────────────────┘ │ WeekendRate │ └──────────────────────────────┘ ┌─────────────────────┐ <<interface>> ┌──────────────────────────────┐ │ PaymentProcessor │◄── implements ───── │ CashPaymentProcessor │ │ + process(amount) │ │ CardPaymentProcessor │ └─────────────────────┘ │ EWalletPaymentProcessor │ └──────────────────────────────┘ ┌───────────────────────────────────┐ │ Ticket │ │ - id: UUID │ │ - vehicle: Vehicle │ │ - spot: ParkingSpot │ │ - entryTime: Instant │ │ - exitTime: Instant (nullable) │ │ - status: TicketStatus │ └───────────────────────────────────┘

Patterns applied

PatternApplied toWhy
SingletonParkingLotOnly one parking lot instance in the system
StrategyRateStrategySwap hourly, daily, weekend rates without changing billing logic
StrategyPaymentProcessorSwap cash, card, e-wallet without changing payment flow
Factory MethodVehicleFactoryDecouple vehicle creation from parking logic
ObserverParkingDisplayBoardAutomatically updates when spot status changes
📚

Example: Library Management System

Demonstrates catalog search, borrowing lifecycle, and fine calculation
java — core classes
// ── Entities ── public class Book { private String isbn; private String title; private List<String> authors; private String publisher; private int totalCopies; } public class BookItem { // physical copy of a Book private String barcode; private Book book; private BookStatus status; // AVAILABLE, BORROWED, RESERVED, LOST private Location shelfLocation; } public class Member { private String memberId; private String name; private MembershipType type; // STUDENT, FACULTY, PUBLIC private List<Lending> activeLoans; private int fineBalance; // in pence } public class Lending { private String lendingId; private BookItem bookItem; private Member member; private Instant borrowDate; private Instant dueDate; private Instant returnDate; // null if not yet returned } // ── Interfaces ── public interface BookSearch { List<Book> searchByTitle(String title); List<Book> searchByAuthor(String author); Book searchByIsbn(String isbn); } public interface LendingService { Lending borrowBook(String memberId, String barcode) throws BookNotAvailableException, MemberFineOutstandingException; void returnBook(String lendingId) throws LendingNotFoundException; int calculateFine(String lendingId); // returns amount in pence } public interface FineStrategy { int calculateFine(long overdueDays, MembershipType memberType); } // Concrete fine strategies — open for extension public class StandardFineStrategy implements FineStrategy { // 10p/day } public class FacultyFineStrategy implements FineStrategy { // 5p/day } public class ZeroFineStrategy implements FineStrategy { // no fines }
🛗

Example: Elevator System

Demonstrates state machine, scheduling algorithm, and concurrency design

The elevator system is a classic LLD problem because it requires a state machine, a non-trivial scheduling algorithm, and careful concurrency thinking. Here's how to structure it.

java — elevator core classes
public enum Direction { UP, DOWN, IDLE } public enum DoorStatus { OPEN, CLOSED } public enum ElevatorState{ MOVING, STOPPED, MAINTENANCE, IDLE } public class ElevatorRequest { private int sourceFloor; private int destinationFloor; private Direction direction; private Instant requestTime; } public class Elevator { private int id; private int currentFloor; private Direction direction; private ElevatorState state; private DoorStatus doorStatus; private TreeSet<Integer> upQueue; // floors to visit going up private TreeSet<Integer> downQueue; // floors to visit going down public synchronized void addRequest(int floor) { ... } public synchronized void move() { ... } public synchronized void openDoor() { ... } public synchronized void closeDoor() { ... } } // ── Scheduler interface — swappable algorithm ── public interface ElevatorScheduler { Elevator selectElevator( List<Elevator> elevators, ElevatorRequest request ); } // Concrete schedulers public class NearestCarScheduler implements ElevatorScheduler { // Select elevator with fewest floors away from request } public class SCANScheduler implements ElevatorScheduler { // Classic elevator algorithm — sweep up, then down } // ── Controller ── public class ElevatorController { private final List<Elevator> elevators; private final ElevatorScheduler scheduler; public void handleRequest(ElevatorRequest request) { Elevator chosen = scheduler.selectElevator(elevators, request); chosen.addRequest(request.getDestinationFloor()); } }

State machine for Elevator

state machine — Elevator states
[*] ──startUp()──► IDLE │ requestReceived()│ doorTimeout() ▼ ────────────── MOVING ──── arrivedAtFloor() ──► STOPPED ▲ │ │ doorClosed() │ └─────────────────────────────────┘ IDLE ──── maintenanceMode() ──► MAINTENANCE ──── repairComplete() ──► IDLE
⚠️

Common Pitfalls

Mistakes that undermine an LLD document — and how to avoid them
God Classes

A single class with 20+ methods and responsibilities across multiple domains. The ParkingLotManager that finds spots, issues tickets, bills, processes payments, and sends emails. Split it. Each responsibility gets its own class.

Inheritance Abuse

Inheriting for code reuse rather than for true "is-a" relationships. ParkingSpot extending DatabaseRecord because both have an id — that's not inheritance, that's code sharing. Use composition: ParkingSpot has persistence logic via a repository.

Missing Interfaces

Writing concrete classes directly without defining interfaces first. This makes components impossible to mock in tests and creates tight coupling. Every service used by another component should be behind an interface.

Ignoring Concurrency

Designing a parking lot with a shared spot map but not noting that checkIn() must be thread-safe. Two gates could assign the same spot simultaneously. Call out which methods need synchronization in the LLD.

Pattern Overuse

Applying a Factory to create a Ticket that has no variation. Patterns add complexity — only introduce one when it solves a real extensibility or decoupling problem you actually have.

No Error Handling Design

Defining happy-path methods but not specifying what happens on failure. What exception does checkIn() throw when the lot is full? Is it checked or unchecked? What's the caller expected to do? These decisions belong in the LLD.

Attributes as Classes

Creating a FirstName class and a LastName class when a name: String attribute on Member is sufficient. Not every noun needs its own class. Only promote an attribute to a class when it has its own behavior or when it's shared across multiple classes.

Skipping the Requirements Section

Jumping straight to drawing classes. Requirements define what use cases your design must support — skipping them means your design may not support the right things, and reviewers have no basis to evaluate it.

🚫
The "I'll figure it out in code" trap: LLD is the right moment to discover design flaws — not during code review. Reviewers frequently catch issues in LLD (wrong relationship types, missing error paths, incorrect cardinality) that would be expensive to fix mid-implementation. Invest the time upfront.

LLD Review Checklist

Validate your design before handing it off for implementation
CategoryCheckSeverity
RequirementsAll functional requirements are covered by at least one class/methodMUST
RequirementsNon-functional requirements (thread safety, performance) are addressedMUST
ClassesNo god class — each class has a single clear responsibilityMUST
ClassesAll service/manager classes are defined behind an interfaceMUST
ClassesAll enumerations and value objects are defined before classesMUST
RelationshipsEvery relationship is typed correctly (inheritance vs. composition vs. association)MUST
RelationshipsMultiplicities are specified (1..*, 0..1, 1..1)SHOULD
DiagramsClass diagram covers all identified entitiesMUST
DiagramsAt least 2 sequence diagrams covering primary use casesMUST
DiagramsState machine for any entity with a lifecycle (Ticket, Order, Spot)SHOULD
ContractsAll public methods have typed parameters, return types, and declared exceptionsMUST
ContractsException hierarchy defined (base exception + specific subtypes)SHOULD
ExtensibilityAdding a new type (vehicle, payment) requires no changes to existing classesSHOULD
PatternsAll patterns used are named and justified in the documentSHOULD
ConcurrencyShared mutable state identified and synchronization strategy documentedSHOULD
SOLIDSRP — no class has multiple reasons to changeMUST
SOLIDOCP — new behaviors can be added via extension, not modificationMUST
SOLIDDIP — high-level modules depend on interfaces, not concrete classesMUST
🎯

LLD Interview Tips

How to approach LLD problems in technical interviews
Time allocation (45 min interview)
  • 5 min — Clarify requirements, ask functional and non-functional questions
  • 5 min — Identify entities, enums, and relationships out loud
  • 15 min — Draw the class diagram skeleton (names + relationships first, then attributes, then methods)
  • 10 min — Walk through 1–2 key sequence diagrams
  • 5 min — Call out design patterns used and why
  • 5 min — Discuss extensibility, concurrency, trade-offs
Common interview systems to practice
  • Parking Lot — spots, tickets, billing, gates
  • Library Management — books, members, lending, fines
  • Elevator System — scheduling, state machine, concurrency
  • Chess Game — board, pieces, move validation, game state
  • Ride-Sharing (Uber/Lyft) — trip lifecycle, matching, pricing
  • Hotel Booking — rooms, reservations, pricing, availability
  • ATM Machine — authentication, transactions, cash dispensing
  • Shopping Cart / E-commerce — catalog, cart, checkout, payment
  • Food Delivery (Swiggy/DoorDash) — orders, restaurants, riders
  • Movie Ticket Booking (BookMyShow) — seats, shows, payments
What interviewers actually evaluate: (1) Do you clarify before designing? (2) Can you decompose a problem into cohesive, loosely coupled classes? (3) Do you know when and why to apply design patterns? (4) Do you proactively discuss extensibility, thread safety, and trade-offs? (5) Can you receive feedback and adapt your design mid-interview? Correctness of every method signature matters less than demonstrating strong design thinking.
Say your decisions out loud. Interviewers want to hear your reasoning, not just see the final diagram. When you choose composition over inheritance, say why. When you add the Strategy pattern, explain what problem it solves. When you make something an enum vs. a class, justify it. Silent designing leaves interviewers with no signal.

Quick-reference: questions to ask at the start

interview — clarifying questions
// FUNCTIONAL "What are the primary use cases — what must the system allow users to do?" "Are there different user roles with different permissions?" "What are the different types of [vehicle / room / book / piece]?" "What happens when [the lot is full / a book is already borrowed]?" // NON-FUNCTIONAL "Should we handle concurrency — multiple requests simultaneously?" "Do we need persistence, or is in-memory sufficient for this design?" "What scale — single building, city-wide, global?" // SCOPE "Is [online reservations / real-time tracking / third-party payments] in scope?" "Should I focus on the core booking flow, or the entire system end-to-end?"