What you'll learn
- Quick answer
- What an LLD Round Is Actually Testing
- A Framework You Can Reuse for Any LLD Question
- Worked Example: Design a Parking Lot
- Worked Example: Design an Elevator System
- Design Patterns That Actually Belong Here (and Mistakes That Sink Candidates)
- How Interviewers Actually Score This Round
- FAQ
Quick Answer
Low-level design (LLD) interviews test whether you can turn a vague prompt like “design a parking lot” into a clean, extensible class model — not whether you know a framework. You clarify requirements, pick the right entities and relationships, write real method logic for the 1–2 core flows, and defend the design when the interviewer changes a requirement mid-conversation. Below are two fully worked examples — a parking lot and an elevator system — with class designs that were actually compiled and run, not just sketched.
What an LLD Round Is Actually Testing
A low-level design interview asks you to design the internal object model of a single system or component in code-level detail — classes, interfaces, relationships, and method signatures — not servers, load balancers, or databases spread across machines. That's the system design (HLD) round's job. LLD stays on one machine and lives at the level of "what objects exist, what do they know, and what can they do." You'll usually get 45–60 minutes, a shared doc or whiteboard, and a deliberately open-ended prompt: design a parking lot, an elevator system, a library management system, a vending machine, a chess game.
What's actually being scored underneath that open-ended prompt: whether you identify the right entities and relationships (inheritance for is-a, composition for has-a, interfaces for behavior contracts), whether your design follows basic SOLID principles without you having to be told to, whether the design survives a twist the interviewer throws in halfway through ("now support EV charging spots," "now support a valet flow"), and whether the code you sketch would actually compile and behave correctly on the edge cases — a full lot, an invalid ticket, a spot that fits more than one vehicle type.
This round shows up constantly in Indian product-based hiring — Amazon, Uber, Ola, Flipkart, Swiggy, and Myntra all run LLD rounds for SDE-2 and above, often alongside or instead of a pure DSA round. It's popular with interviewers precisely because it's language-agnostic and hard to fake with memorized answers: there's no single "correct" class diagram for a parking lot, so the interviewer is watching your reasoning, not checking it against an answer key.
A Framework You Can Reuse for Any LLD Question
The prompts vary, but the process that gets you through them doesn't. Use the same six steps whether you're asked about a parking lot, a library system, or a ride-hailing dispatcher.
- Clarify scope out loud. Functional scope (does the parking lot need multiple floors? Multiple vehicle types? Reservations?) and non-functional hints (should spot assignment be thread-safe for concurrent entries?). Never start writing classes on a prompt you haven't pinned down — five minutes here saves a rewrite at minute thirty.
- Identify the nouns. Pull the entities and actors straight out of the requirements, and decide which become classes, which become enums, and which are just fields. A "vehicle type" that only ever takes three fixed values is an enum, not a class hierarchy.
- Define relationships before methods. Inheritance for genuine
is-arelationships, composition forhas-a(aParkingLothasParkingFloors, which haveParkingSpots), interfaces for behavior contracts you expect multiple types to implement (Payable,Notifiable). Getting this wrong is the single most common way an LLD design collapses under a follow-up question. - Sketch skeletons, then flesh out the 1–2 core flows. Every class doesn't need every getter and setter filled in during the interview. Pick the flows the interviewer clearly cares about — assign a spot, dispatch an elevator — and write real logic for those.
- Trace concrete scenarios through your own code. This is the step candidates skip and it's the one that actually catches bugs. Before you say you're done, walk a specific input through your own method by hand. Both worked examples below were traced exactly this way — run against real inputs, not just described.
- Raise extensibility and edge cases yourself. Don't wait to be asked "what if the lot is full" or "what if someone tries to add a fourth vehicle type." Naming these before the interviewer does is a stronger signal than answering them reactively.
Worked Example: Design a Parking Lot
Requirements first: a lot with multiple floors, spots of different sizes (small, medium, large), vehicles of different types (motorcycle, car, bus) with size-fit rules, a ticket issued on entry, a fee computed on exit, and graceful handling when the lot is full for a given vehicle type. A motorcycle fits any spot; a car needs at least a medium spot; a bus needs a large spot.
The one design decision worth calling out explicitly to the interviewer: spot assignment should be best-fit, not first-fit. If you hand a motorcycle the one remaining large spot, you've just made every future bus turn away. The findBestFitSpot method below scans free spots the vehicle is eligible for and picks the smallest one — that's the whole trick, and it's worth saying out loud unprompted, because it's exactly the kind of subtlety interviewers probe for.
Here's the core design — enums for the fixed vocab, a fit rule, and the three methods that matter (findBestFitSpot, parkVehicle, unparkVehicle). Getters, imports, and the Vehicle/Ticket data classes are trimmed for space, but this exact logic was compiled and run against a test scenario, not just written from memory:
enum VehicleType { MOTORCYCLE, CAR, BUS }
enum SpotSize { SMALL, MEDIUM, LARGE }
static boolean fits(VehicleType type, SpotSize size) {
switch (type) {
case MOTORCYCLE: return true; // fits any spot
case CAR: return size == SpotSize.MEDIUM || size == SpotSize.LARGE;
case BUS: return size == SpotSize.LARGE;
default: return false;
}
}
class ParkingSpot {
String id;
SpotSize size;
boolean occupied = false;
Vehicle vehicle;
}
class ParkingFloor {
int floorNumber;
List<ParkingSpot> spots = new ArrayList<>();
}
class ParkingLot {
List<ParkingFloor> floors = new ArrayList<>();
Map<String, Ticket> activeTickets = new HashMap<>();
int ticketCounter = 1;
// Best-fit: among free spots the vehicle fits into, pick the smallest one.
// Don't waste a LARGE spot on a MOTORCYCLE if a SMALL one is free.
ParkingSpot findBestFitSpot(VehicleType type) {
ParkingSpot best = null;
for (ParkingFloor floor : floors) {
for (ParkingSpot spot : floor.spots) {
if (!spot.occupied && fits(type, spot.size)) {
if (best == null || spot.size.ordinal() < best.size.ordinal()) {
best = spot;
}
}
}
}
return best;
}
Ticket parkVehicle(Vehicle vehicle, LocalDateTime now) {
ParkingSpot spot = findBestFitSpot(vehicle.type);
if (spot == null) return null; // lot full for this vehicle type
spot.occupied = true;
spot.vehicle = vehicle;
String ticketId = "T" + (ticketCounter++);
Ticket ticket = new Ticket(ticketId, spot.id, vehicle, now);
activeTickets.put(ticketId, ticket);
return ticket;
}
double unparkVehicle(String ticketId, LocalDateTime now) {
Ticket ticket = activeTickets.remove(ticketId);
if (ticket == null) {
throw new IllegalArgumentException("Invalid or already-closed ticket: " + ticketId);
}
// free the spot, then bill by the hour, rounded up
long minutes = Duration.between(ticket.entryTime, now).toMinutes();
double hours = Math.ceil(minutes / 60.0);
return hours * hourlyRate(ticket.vehicle.type);
}
}The test scenario: a floor with 2 small, 2 medium, and 1 large spot. Park a motorcycle, two cars, and a bus — that fills every medium and large spot. A third car should then be turned away, but a second motorcycle should still fit in the remaining small spot. Unpark the first car after 2 hours 15 minutes at a ₹30/hour rate, and it should bill 3 hours (rounded up) for ₹90. Trying to unpark the same ticket again should throw. Here's the real, unedited output from running exactly that:
Bike parked at spot: F1-S1 ticket=T1
Car1 parked at spot: F1-M1 ticket=T2
Car2 parked at spot: F1-M2 ticket=T3
Bus parked at spot: F1-L1 ticket=T4
Car3 parking result (expect null, lot full for cars): null
Bike2 parked at spot: F1-S2
Car1 fee for 2h15m (rate 30/hr, rounded up to 3h): 90.0
Car3 parked after car1 left, at spot: F1-M1
Got expected exception on double-unpark: Invalid or already-closed ticket: T2Every line matches what the design predicts: best-fit assignment (bike to a small spot, cars to medium, bus to large), a clean null instead of a crash when the lot is full for cars, the freed spot reused correctly, and the double-unpark case throwing instead of silently succeeding. That last one is a detail interviewers specifically listen for — a design that doesn't account for it usually means the candidate never traced their own edge cases.
Worked Example: Design an Elevator System
Requirements: N elevators serving F floors, two kinds of requests — a hall call (someone on a floor presses up or down) and a car call (someone already inside picks a destination floor) — and a dispatch algorithm that assigns hall calls to elevators in a way that minimizes wait time without starving anyone.
The trap most candidates fall into: assigning the nearest elevator by raw floor distance, ignoring direction. An elevator six floors below a call but already heading down is a worse pick than one three floors below but idle, because the first one has to finish its sweep and reverse before it can help. Real elevator systems use a SCAN-style approach (sometimes called the LOOK algorithm): each elevator sweeps in one direction serving every call along the way, then reverses, and dispatch prefers whichever elevator can pick up a call "on the way" over one that would need to reverse.
The design encodes that directly as a cost function — an elevator moving toward the call in the right direction costs just the floor distance; one that would have to finish its sweep and reverse pays a heavy penalty:
enum Direction { UP, DOWN, IDLE }
class Elevator {
int id;
int currentFloor;
Direction direction = Direction.IDLE;
// Cost to serve a hall call at `floor` heading `wantDirection`. Lower is better.
int costToServe(int floor, Direction wantDirection) {
if (direction == Direction.IDLE) {
return Math.abs(currentFloor - floor);
}
boolean sameDirection = direction == wantDirection;
boolean onTheWay = sameDirection &&
((direction == Direction.UP && floor >= currentFloor) ||
(direction == Direction.DOWN && floor <= currentFloor));
if (onTheWay) {
return Math.abs(currentFloor - floor);
}
// Not on the way: must finish the current sweep, reverse, then come back.
return 1000 + Math.abs(currentFloor - floor);
}
}
class ElevatorController {
List<Elevator> elevators;
Elevator assignBestElevator(int floor, Direction wantDirection) {
Elevator best = null;
int bestCost = Integer.MAX_VALUE;
for (Elevator e : elevators) {
int cost = e.costToServe(floor, wantDirection);
if (cost < bestCost) {
bestCost = cost;
best = e;
}
}
best.addStop(floor);
return best;
}
}Three elevators: #1 idle at floor 5, #2 idle at floor 1, #3 already moving up from floor 9 with a stop queued at floor 12. A hall call comes in at floor 7, direction up. Here's the real output — first the per-elevator cost breakdown for that call, then the assignment decisions for three separate calls:
Elevator 1 (idle @5) cost: 2
Elevator 2 (idle @1) cost: 6
Elevator 3 (moving UP @9) cost: 1002
Call at floor 7 going UP assigned to elevator: 1
Call at floor 2 going UP assigned to elevator: 2
Call at floor 11 going UP assigned to elevator: 3Elevator 1 wins the floor-7 call at cost 2 over elevator 2's cost 6 (both idle, pure distance) and elevator 3's cost 1002 (already past floor 7 heading up — it would have to reverse). A call at floor 2 correctly goes to elevator 2 (nearest idle car). A call at floor 11 correctly goes to elevator 3, because 11 is on the way to its already-queued stop at 12 — no reversal needed, so it beats both idle elevators despite not being the nearest by raw distance. That's the whole algorithm proven on three concrete calls, not just asserted.
Design Patterns That Actually Belong Here (and Mistakes That Sink Candidates)
A handful of design patterns show up naturally in LLD interviews because they solve real problems these systems have, not because you're expected to name-drop them. Strategy fits a pricing or dispatch rule that needs to vary (a weekday vs weekend parking rate, or swapping the elevator dispatch algorithm for a different building's traffic pattern) without an if/else ladder growing forever. Factory fits object creation when callers shouldn't know the concrete class (creating the right ParkingSpot subtype for a size, or the right Vehicle subtype from an input string). Observer fits anything that needs to react to a state change without tight coupling — a display board updating when a floor's spot availability changes. Singleton fits a single shared ParkingLot or ElevatorController instance, but say out loud that you know it needs thread-safety (an enum-based singleton or double-checked locking in Java) and that it makes unit testing harder — mentioning the trade-off is worth more than just naming the pattern.
The mistakes that actually cost points, in the order interviewers see them most:
- Jumping to code before clarifying requirements. A confident class diagram built on a wrong assumption about scope is worse than five minutes of questions.
- Resume-driven design — forcing in a pattern you memorized (a Visitor here, a Decorator there) without a real problem it solves. Interviewers ask "why this pattern" and "what breaks without it" specifically to catch this.
- Ignoring concurrency entirely. Two threads both calling
findBestFitSpotcan both find the same free spot before either marks it occupied. Saying so, and naming the fix (make find-and-reserve one atomic step, guarded by a lock on the spot or a compare-and-swap on its occupied flag) is a strong signal even if you don't have time to fully implement it. - Not handling the edge cases your own design implies. A full lot, an invalid ticket, a vehicle type with no matching spot size — the parking lot code above handles all three explicitly (a clean
null, a thrown exception with a clear message) because those are exactly the follow-ups an interviewer asks next. - Writing method signatures with empty bodies and talking around the logic instead of writing it, when the interviewer specifically asked you to implement a method. If you're going to hand-wave anything, hand-wave the getters — never the core flow.
How Interviewers Actually Score This Round
Most LLD rubrics score across the same handful of axes: requirement gathering and scoping, correctness of your OOP relationships and SOLID adherence, extensibility under a follow-up requirement, code quality and whether it would plausibly compile, and how clearly you communicate while you design. Note what's missing from that list: pixel-perfect syntax. Most interviewers will forgive a missing semicolon. Very few will forgive a design that needs three classes rewritten because you hard-coded vehicle types as an enum with no room to add a fourth without touching every switch statement that branches on it.
The single highest-signal moment in most LLD interviews is the follow-up twist — "now add EV charging spots," "now support a valet who parks the car in any spot, not just the one assigned." A design built around composition and interfaces (a SpotType that spots and vehicles both reference through a shared fits() contract, rather than a hard-coded switch statement duplicated in three places) absorbs this in under two minutes of talking. A design that hard-coded the three original vehicle types everywhere needs a rewrite, and that difference is usually worth more to your score than anything that happened in the first thirty minutes.
