Low-level design
How to run the forty-five minutes
An LLD round is a timed conversation that ends in working object code. You clarify use cases and scope first, agree a tiny public API with real types, name the model, then implement. Candidates who freeze try to recall a perfect class diagram. Candidates who finish talk before they type.
We already know this parking lot is in-process design, not a distributed system. Now you need a talking order so the last thirty minutes are coding, not panic. Ask what is out of scope early — refunds, a mobile app, multiple cities — then set up the API and code.
Working order
Clarify
Use cases, one program, what is out.
API
Commands and queries with real types.
Model
Entities, values, who owns whom.
Implement
Happy path, then one bad ticket.
Visible artifacts
By each time marker, something exists on the board
This prevents long silent thinking. The diagram can stay messy. The cannot.
5 minutes
Clarify use cases and what is out
10 minutes
Agree the public API with real types
20 minutes
Name types, values, who owns whom
35 minutes
Happy path plus one illegal case
45 minutes
Show the seam for the extra they add
Talk before you type
Say the use cases out loud. Confirm who acts: driver, admin, two gates. Confirm : what you will not build today. Write the public API next. park takes a vehicle and returns a ticket. exit takes a ticket id and returns a receipt. That API is the contract you will implement.
Then list the things that exist and the one rule that must not break: a spot is held by at most one vehicle. Only then mention a plan for “which free spot?” or “how much to charge.” Mention it only if they hinted those rules will change. Strategy and Factory are tools. They come after you know who allocates and who prices. Opening with a pattern name wastes the first ten minutes.
A working order is: types and enums, the API, the , one (lot full, unknown ticket), then the they always add. Even a crude test keeps you honest when you split a Manager apart. Do not wait for a perfect picture. Talk, agree the API, then code.
Conversation map
Drive, then invite depth
Treat the round as checkpoints. At each checkpoint, say what you are assuming and ask whether they want to adjust scope. This keeps you from over-building the wrong lot.
Agree this API before you type the rest
If park and exit are still fuzzy, you will rewrite the core. Speak the contract. Get a nod. Then implement. process(Object) is not an API. Real types force you to name Ticket, Vehicle, and Receipt early, which is half the model.
class ParkingService {
private final ParkingLot lot;
private final SpotChooser chooser;
private final TicketBook tickets;
private final Pricing pricing;
private final Clock clock;
Ticket park(Vehicle vehicle) {
ParkingSpot spot = chooser.choose(vehicle)
.orElseThrow(LotFullException::new);
spot.occupy(vehicle);
return tickets.issue(vehicle, spot, clock.instant());
}
Receipt exit(TicketId ticketId) {
Ticket ticket = tickets.require(ticketId);
Money fee = pricing.fee(ticket, clock.instant());
ticket.spot().vacate();
tickets.close(ticket);
return new Receipt(ticket.id(), fee);
}
}You can sketch this on a whiteboard as signatures only for the first ten minutes. Empty method bodies are fine. The point is agreement. The requirements-to-classes page is how you fill the types behind these signatures without a Manager.
A transcript of a round that stays on the rails
You
I will design an in-process parking lot. Use cases: park and exit. Out of scope: payments company, phone app, multiple cities. Two gates means two threads sharing spots. Is that the right altitude?
Interviewer
Yes. Start with cars and bikes. We may add EV later.
You
Public API: Ticket park(Vehicle), Receipt exit(TicketId). Invariant: a spot holds at most one vehicle. I will put occupy and vacate on the spot. Pricing and spot choice will be small types so EV can land later without editing the lot.
Interviewer
How do you pick a free spot?
You
SpotChooser. Nearest-empty today. If you want EV-only stalls, that is a second implementation, not a new if in park.
You
I will code the happy path next, then lot-full, then we can add hourly EV pricing.
Notice the candidate never opened with “Factory plus Strategy.” They placed jobs, named the seam, and asked permission before going deeper. That is the round.
Implement in this order, not in pattern order
- Enums and value types: VehicleType, Money, TicketId. They make the API readable.
- Entities with one job: ParkingSpot.occupy, Ticket as a record of a stay.
- Collaborator interfaces: SpotChooser, Pricing, TicketBook.
- ParkingService walking those collaborators on the happy path.
- LotFullException and UnknownTicketException as types, not strings.
- If time remains, a second Pricing or a synchronized occupy for two gates.
If you feel lost
Return to the contract. Say park and exit again. Ask which verb has no home. Assign that verb. Then type. Silent staring at a blank class list is how the Manager comes back — every leftover method dumped into one file so something compiles.
One illegal case, written as a type
Interviewers watch whether failure is designed or shrugged. A dedicated exception (or a Result type) shows the API has edges. A string message inside a generic runtime exception does not.
class LotFullException extends RuntimeException {
LotFullException(VehicleType type) {
super("no free spot for " + type);
}
}
class UnknownTicketException extends RuntimeException {
UnknownTicketException(TicketId id) {
super("unknown ticket " + id.value());
}
}
Optional<ParkingSpot> choose(Vehicle vehicle); // empty means full
Ticket require(TicketId id) {
Ticket ticket = byId.get(id);
if (ticket == null || ticket.closed()) {
throw new UnknownTicketException(id);
}
return ticket;
}Checkpoints you can reuse
Clarify
API
Model
Assign
Implement
Extend
Two mistakes that steal the first ten minutes
Jumping to a pattern
Strategy and Factory are tools. They come after you know who allocates and who prices. Opening with a pattern name wastes the first ten minutes and hides the missing owner.
Coding before the API is agreed
If park and exit are still fuzzy, you will rewrite the core. Speak the contract. Get a nod. Then implement. Quiet typing looks like guessing, which the scorecard punishes.
Continue from here
- Requirements to classes — turn the owner’s paragraph into a class list you can defend.
- Responsibility assignment — after the list exists, give each verb a home.
- Parking lot case — run this order on the full problem.
- HLD versus LLD — if you feel Redis returning, go back to in-process design.
Takeaway
Talk first. Agree scope and a tiny API. Name types. Assign verbs. Code the happy path, then one illegal case, then show where the follow-up lands. The diagram can stay messy. The contract cannot. Next we take a messy requirements paragraph — vehicles enter, get a ticket, leave, pay by time — and turn it into a class list that does not include ParkingLotManager.