What is the difference between HLD and LLD?
HLD answers questions such as: Where does data live? Which operations are synchronous? How does traffic reach the right service? What happens when a dependency fails? Its scope is usually the system and the boundaries between components.
LLD answers questions such as: What does this method accept and return? Which data structure supports the required operations? What state changes are legal? How do concurrent requests avoid corrupting data? Its scope is usually a component, module, or specific execution path.
The boundary is not fixed. An API contract or database schema can appear in either discussion. What matters is whether you are explaining the system-wide responsibility or the precise behavior needed to implement it.
- HLD example: Route rate-limit checks through a shared store so application instances enforce one account-wide limit.
- LLD example: Define the token-bucket state and the atomic operation that refills tokens and admits or rejects a request.
- HLD correctness question: Can a regional outage cause the system to exceed its global limit?
- LLD correctness question: Can two simultaneous requests both consume the last available token?
What should each design include?
HLD and LLD produce different artifacts, but neither is just a diagram. Every artifact should explain a requirement, a decision, or a failure case.
- HLD structure: Clients, services or modules, databases, caches, queues, external dependencies, and their responsibilities.
- HLD flows: The main read and write paths, including synchronous calls, asynchronous work, and data ownership.
- HLD constraints: Relevant traffic and storage estimates, latency goals, consistency requirements, bottlenecks, and recovery behavior.
- LLD contracts: Public interfaces, inputs, outputs, validation, errors, and retry behavior.
- LLD internals: Data structures, algorithms, object or module responsibilities, and state transitions.
- LLD correctness: Atomic operations, transaction boundaries, locking, resource cleanup, and tests.
What interviewers expect at each level
In an HLD interview, start with requirements and the end-to-end flow. Interviewers commonly probe why a component exists, what limits its capacity, and how the design behaves under failures. Naming a cache, queue, or database is not enough; explain what it stores and which guarantees it provides.
In an LLD interview, make behavior precise enough to implement. Depending on the prompt, that may mean interfaces and pseudocode, a class diagram, or working code. Interviewers can then probe extensibility, edge cases, algorithmic complexity, and thread safety.
Some teams use LLD to mean object-oriented design or machine coding, while others mean a detailed subsystem design. Ask what output is expected before committing to class diagrams or infrastructure.
- Clarify the scope: “Should I design the distributed service, implement one component, or cover both?”
- Clarify the deliverable: “Would you like interfaces and pseudocode, or executable code?”
- Clarify concurrency: “Will this run in one thread, multiple threads, or across multiple processes?”
- Clarify priorities: “Which matters most here: correctness, throughput, extensibility, or implementation completeness?”
How to move from high-level design to low-level design
Begin with the requirements and invariants, then draw the smallest architecture that supports them. Walk one important operation from entry to completion before adding optimizations.
Next, choose a component whose details determine whether the design works. Define its contract, internal state, and failure behavior. Return to the architecture if those details expose a missing guarantee.
- State the invariant, such as “A seat cannot belong to two confirmed bookings.”
- Assign ownership: Identify which component and data store enforce that invariant.
- Trace the normal path: Show requests, reads, writes, and responses in order.
- Specify the critical operation: Define its inputs, state changes, and atomicity requirements.
- Test the failure path: Consider concurrent requests, timeouts, retries, and process crashes.
- Revisit the architecture: Add coordination or change a boundary only when the requirements justify it.
Example: HLD and LLD for a rate limiter
At the HLD level, decide where enforcement happens, how clients are identified, where policies live, and whether limits are local or shared across instances. A shared store can coordinate decisions across instances, but it adds a network dependency to the request path. Explain whether a store outage allows requests through or rejects them, and why that policy fits the endpoint.
At the LLD level, a token-bucket interface might be checkAndConsume(key, cost), returning allowed, remainingTokens, and retryAfter. Store the token balance and last-refill timestamp per key. Within one atomic operation, refill the balance up to capacity, check the cost, and deduct tokens if sufficient capacity exists.
A mutex protects only callers sharing that mutex in one process. For a shared bucket used by many servers, use a store-supported atomic operation or transaction. A separate read followed by a write can admit too many requests under contention.
- Define refill behavior: tokens = min(capacity, storedTokens + refillRate × elapsedTime).
- Specify the time source and handling of backward clock movement; do not assume every server's wall clock agrees.
- Validate cost and policy values, including whether a request costing more than bucket capacity is always rejected.
- Choose expiry so deleting an idle bucket cannot grant tokens earlier than normal refill would.
- Test simultaneous requests, refill boundaries, exhausted capacity, policy changes, and storage timeouts.
- Expect a follow-up on hot keys: distributing requests across servers does not remove contention on one shared bucket.
Trade-offs to say out loud
A strong answer connects a system-level choice to its implementation consequences. Avoid claims such as “a queue guarantees reliability” or “a lock prevents races” without naming the scope and failure model.
For example, choosing asynchronous notification delivery changes the user-visible contract to acceptance rather than immediate delivery. The implementation then needs durable work, explicit delivery states, retry rules, and protection against duplicate effects.
- Local state versus shared state: Local decisions avoid a network call but cannot by themselves enforce a strict cross-instance invariant.
- Synchronous versus asynchronous work: Background processing can shorten the request path, but introduces delayed completion, retries, and status tracking.
- Optimistic versus pessimistic concurrency: Version checks avoid holding locks while work proceeds, but conflicting operations may need retries; locks introduce waiting and possible deadlocks.
- Strict versus approximate limits: Local token allocations can reduce central coordination, but the allocation and failover rules determine whether overshoot is possible.
- General interfaces versus simple code: Add extension points for likely variation, not for every hypothetical future requirement.
- Durability versus latency: Acknowledging before durable persistence is faster in some designs but can lose accepted work after a crash.
Common mistakes when comparing LLD vs HLD
The most common mistake is choosing the wrong level of detail for the prompt. Designing a fleet of services does not answer an exercise asking for an in-memory cache implementation. Writing getters and setters does not explain how a distributed booking service prevents overselling.
Another mistake is treating HLD as nontechnical and LLD as merely coding. HLD requires precise reasoning about data flow and guarantees. LLD requires design judgment about responsibilities, contracts, and correctness, not just syntax.
- Do not force microservices into HLD. A modular application may satisfy the requirements with fewer operational costs.
- Do not force inheritance into LLD. Functions, composition, and small interfaces are often enough.
- Do not postpone all correctness questions to implementation. Architecture must establish who can enforce each invariant.
- Do not assume a process-local lock coordinates different servers.
- Do not add caches without explaining staleness and invalidation.
- Do not claim retry safety without explaining how repeated requests are recognized and handled.
How to practice both levels together
Use the same problem twice. First explain the architecture and trace a request through it. Then zoom into the component carrying the main correctness risk and specify it well enough to implement.
Review whether your detailed design actually delivers the guarantees promised by the architecture. If you say “no duplicate charges” or “no overselling,” point to the exact state, operation, and storage guarantee that make the statement defensible.
- Rate limiter: Design enforcement across instances, then implement and test one limiting algorithm and its atomic update.
- Ticket booking: Design search, holds, and payment coordination, then specify hold expiry and the race between confirmation and release.
- Distributed cache: Design ownership and replication, then implement a bounded local cache with explicit eviction, expiry, and concurrency behavior.
- Notification system: Design queues and channel workers, then define the delivery state machine, retry policy, and idempotency scope.
- For every exercise, explain one rejected alternative and the requirement that made it a worse fit.
Where it comes up
Questions to practice with this guide.
- Design a distributed rate limiterThis problem connects system-wide enforcement and failure policy to the precise algorithm and atomic operation that decide admission.
- Design a ticket booking system like TicketmasterThe architecture must identify inventory ownership, while the detailed design must resolve concurrent holds, expiry, and payment confirmation.
- Design a distributed cacheCluster ownership and failover are high-level concerns that must connect to detailed eviction, expiry, and concurrency behavior.
- Design a notification systemQueue and worker architecture needs a concrete delivery state machine, retry policy, and idempotency rules to support its reliability claims.
Questions people ask
Is low-level design harder than high-level design?
Neither is inherently harder. HLD emphasizes broad trade-offs, capacity, data ownership, and distributed failures; LLD emphasizes precise contracts, algorithms, state, and concurrency. Which feels harder depends on your experience and the interview's expected depth.
Should I learn HLD or LLD first?
You can learn them together, but basic programming, data structures, and interface design make both easier. For a specific interview, prioritize its stated format. Practicing one problem at both levels helps you connect architectural promises to implementation guarantees.
Does an LLD interview require code or UML diagrams?
It depends on the interview format. Some interviews expect working code, while others accept interfaces, pseudocode, sequence diagrams, or class diagrams. Confirm the expected deliverable, and use diagrams only when they clarify responsibilities or behavior.