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.
What is Low-Level Design?
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?"
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.
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.
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.
HLD vs LLD
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 |
What HLD covers that LLD does not
- 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
- 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
- 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
- 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
A complete LLD document contains all information a developer needs to implement the component correctly — no more, no less. The standard sections are:
| # | Section | What it covers | Required? |
|---|---|---|---|
| 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
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.
Step 2 — Classify each entity
Has state and behavior. Can be instantiated. Use for real-world objects: Car, ParkingSpot, Ticket.
Defines a base with shared state, but some behavior is deferred. Use when subclasses share data but specialize behavior: Vehicle with Car, Truck, Motorcycle.
Defines a contract — no state. Use for capabilities that cut across hierarchies: Payable, Notifiable, Serializable. Prefer interfaces over abstract classes.
Fixed set of named constants. Use for states, types, and categories: VehicleType, SpotStatus, PaymentMethod.
Orchestrates behavior but holds little state. Use for use-case logic: ParkingLotManager, BillingService, NotificationService.
Immutable, equality by value not identity. Use for concepts with no identity of their own: Money, Address, DateRange.
Step 3 — Define relationships
| Relationship | Meaning | UML | Example |
|---|---|---|---|
| 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 |
UML Diagrams in LLD
Class Diagram notation
Visibility and stereotypes
+public — accessible from anywhere-private — accessible only within the class#protected — accessible within class and subclasses~package — accessible within the same package
<<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.
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.
API & Interface Contracts
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.
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
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.
In-memory data structure decisions
| Use case | Structure | Why |
|---|---|---|
| Find available spot by type quickly | Map<VehicleType, Queue<ParkingSpot>> | O(1) lookup by type, O(1) pop of next available spot |
| Look up ticket by ID | ConcurrentHashMap<String, Ticket> | Thread-safe O(1) lookup for concurrent checkouts |
| Track all spots on a floor | Map<Integer, List<ParkingSpot>> | O(1) floor lookup, ordered list for display |
| Audit log of all events | LinkedList<ParkingEvent> | Append-only, ordered insertion is O(1) |
How to Create an LLD — Step by Step
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
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.
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.
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).
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.
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.
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.
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
| Pattern | Category | When to Use in LLD | Example |
|---|---|---|---|
| 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 |
SOLID Principles Applied to LLD
Each class has exactly one reason to change. Split mixed concerns into focused classes.
Open for extension, closed for modification. Add new vehicle types or rate strategies without editing existing classes.
Every subclass must honor the contract of its superclass. A Motorcycle must be usable anywhere a Vehicle is expected — same behavioral guarantees.
Don't force implementers to depend on methods they don't use. Split fat interfaces into role-specific ones.
High-level classes depend on abstractions, not concretions. BillingService should depend on RateStrategy (interface), not HourlyRate (concrete class).
Full Example: Parking Lot System
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
- 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
Class diagram (text representation)
Patterns applied
| Pattern | Applied to | Why |
|---|---|---|
| Singleton | ParkingLot | Only one parking lot instance in the system |
| Strategy | RateStrategy | Swap hourly, daily, weekend rates without changing billing logic |
| Strategy | PaymentProcessor | Swap cash, card, e-wallet without changing payment flow |
| Factory Method | VehicleFactory | Decouple vehicle creation from parking logic |
| Observer | ParkingDisplayBoard | Automatically updates when spot status changes |
Example: Library Management System
Example: Elevator System
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.
State machine for Elevator
Common Pitfalls
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.
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.
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.
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.
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.
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.
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.
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.
LLD Review Checklist
| Category | Check | Severity |
|---|---|---|
| Requirements | All functional requirements are covered by at least one class/method | MUST |
| Requirements | Non-functional requirements (thread safety, performance) are addressed | MUST |
| Classes | No god class — each class has a single clear responsibility | MUST |
| Classes | All service/manager classes are defined behind an interface | MUST |
| Classes | All enumerations and value objects are defined before classes | MUST |
| Relationships | Every relationship is typed correctly (inheritance vs. composition vs. association) | MUST |
| Relationships | Multiplicities are specified (1..*, 0..1, 1..1) | SHOULD |
| Diagrams | Class diagram covers all identified entities | MUST |
| Diagrams | At least 2 sequence diagrams covering primary use cases | MUST |
| Diagrams | State machine for any entity with a lifecycle (Ticket, Order, Spot) | SHOULD |
| Contracts | All public methods have typed parameters, return types, and declared exceptions | MUST |
| Contracts | Exception hierarchy defined (base exception + specific subtypes) | SHOULD |
| Extensibility | Adding a new type (vehicle, payment) requires no changes to existing classes | SHOULD |
| Patterns | All patterns used are named and justified in the document | SHOULD |
| Concurrency | Shared mutable state identified and synchronization strategy documented | SHOULD |
| SOLID | SRP — no class has multiple reasons to change | MUST |
| SOLID | OCP — new behaviors can be added via extension, not modification | MUST |
| SOLID | DIP — high-level modules depend on interfaces, not concrete classes | MUST |
LLD Interview Tips
- 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
- 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