Low-level design
What they actually grade besides “used a pattern”
Interviewers grade the object model, responsibility assignment, public API clarity, room for change, two-actor races, and narration. Pattern names are tools after those answers exist — they are not the scorecard. Naming Factory plus Strategy without saying who owns allocation can still score poorly.
You have already learned to split responsibilities, stay in-process, talk through forty-five minutes, and build a class list without a Manager. This page is how those answers are marked.
Scorecard at a glance
- Model — real things, real values, one invariant.
- Jobs — each verb has a home.
- API — park and exit, not process(Object).
- Change — new fee is a new type.
- Two actors — name the race.
- Talk — they grade the conversation too.
The scorecard, written as a table so you can study it
Interviewers rarely hold a printed rubric. They still listen for these six. If you name-drop Factory plus Strategy plus Observer without who owns allocation, you can hit zero on all six at once.
| They listen for | Strong | Weak |
|---|---|---|
| Model | Ticket, spot, vehicle; Money as a value; one vehicle per spot | Helpers, DTOs, and a Manager that is the model |
| Jobs | Chooser allocates, spot occupies, book issues, policy prices | Every answer is “the service” |
| API | Ticket park(Vehicle); Receipt exit(TicketId) | void process(Object request) |
| Change | EV pricing is a new Pricing type; lot stays stable | Another if in exit, edited in five files |
| Two actors | Shared state named; occupy is the atomic step | Ignore two gates, or lock everything |
| Talk | Scope, API, trade-off, next step, spoken | Silent typing; they cannot mark what they never heard |
API clarity is a scored item, not a nicety
First they grade the . Then they grade jobs and the API. Who allocates a spot? Who prices? Who stores the ticket? If every answer is “the service,” responsibilities have collapsed into a . park and exit should be easy to say. process(Object) is not.
Vague — hard to grade, hard to extend
void process(Object request);
Map<String, Object> handle(Map<String, Object> body);
String run(String command, String[] args);Clear — this is what they want to hear first
Ticket park(Vehicle vehicle);
Receipt exit(TicketId ticketId);
// illegal cases are types too
class LotFullException extends RuntimeException { }
class UnknownTicketException extends RuntimeException { }Change is a scored item
Show the gap. Do not recite the pattern catalog.
Can you add EV spots or a new fee without editing the lot in five places? That gap is the point of Strategy later. Saying the word early does not earn the mark. Showing the does. The follow-up is not a surprise. Design as if it will arrive.
interface Pricing {
Money fee(Ticket ticket, Instant exitTime);
}
class HourlyByType implements Pricing {
public Money fee(Ticket ticket, Instant exitTime) { /* hours * type rate */ }
}
class HourlyPlusEvSurcharge implements Pricing {
private final Pricing inner;
private final Money surchargePerHour;
public Money fee(Ticket ticket, Instant exitTime) {
Money base = inner.fee(ticket, exitTime);
if (ticket.vehicle().type() != VehicleType.EV) {
return base;
}
long seconds = Duration.between(ticket.enteredAt(), exitTime).getSeconds();
if (seconds < 0) throw new IllegalArgumentException("negative stay");
long hours = Math.max(1, seconds / 3600 + (seconds % 3600 == 0 ? 0 : 1));
return base.plus(surchargePerHour.times(hours));
}
}
// the lot does not change when the surcharge appears
class ParkingLot {
private final Pricing pricing;
Receipt exit(TicketId id) {
Ticket ticket = tickets.require(id);
Money fee = pricing.fee(ticket, clock.instant());
ticket.spot().vacate();
return new Receipt(ticket.id(), fee);
}
}Two gates: name the race even if you do not lock it yet
If two gates exist, they grade the . Two threads must not hand out the same spot. You do not need a clever lock in minute five. You do need to name the shared state and the one step that must not split. Then, if time remains, put occupy behind a lock or make choose-and-occupy a single critical section.
Two-gate timeline
- T0Gate A and Gate B both call park(car). Chooser sees spot 12 free.
- T1If occupy is a public field write, both set occupied and both issue tickets.
- T2Invariant dies: one stall, two tickets. The model was never protecting itself.
- Fixoccupy() throws if already held, and choose+occupy is synchronized (or a single atomic method on the lot).
class ParkingLot {
public synchronized Ticket park(Vehicle vehicle) {
ParkingSpot spot = chooser.choose(vehicle)
.orElseThrow(LotFullException::new);
spot.occupy(vehicle); // throws if a stale chooser result is already held
return tickets.issue(vehicle, spot, clock.instant());
}
}
class ParkingSpot {
private Vehicle occupant;
synchronized void occupy(Vehicle vehicle) {
if (occupant != null) throw new IllegalStateException("already held");
occupant = vehicle;
}
}synchronized on the whole lot is coarse and fine for an interview lot with tens of spots. Mention that you would refine locking per floor or use compare-and-set if the lot were huge. The mark is that you saw the race. Details live in race conditions.
Talking is on the scorecard
is how they see the design. Confirm scope. Name a trade-off in a short sentence. Silent perfect code still reads as weak, because they cannot mark a design they never heard. Quiet typing looks like guessing.
You
I will keep park and exit as the only public commands. Pricing is a separate type because you said EV might arrive. Two gates share spots, so occupy will be the atomic step. I will code the happy path next unless you want the race first.
Interviewer
What if I add a monthly pass?
You
Then Pricing grows a second implementation that returns zero for pass holders. The lot still calls pricing.fee. I would not put pass logic on ParkingSpot.
Interviewer
Why not a full Observer for SMS?
You
I would not, unless notifying is in scope. A comment that TicketBook.close could later notify is enough. Observer without a real subscriber is unused complexity.
Speech that scores low
- “I will use Factory plus Strategy plus Observer.”
- “Let me just start coding and see.”
- “The Manager can handle all of that.”
- “Threads? We can ignore that.”
- Silence for eight minutes while a class diagram grows.
Speech that scores high
- “In-process, park and exit, EV out of scope unless you add it.”
- “Chooser allocates; spot occupies; book issues.”
- “Pricing is the seam for a new fee.”
- “Two gates: occupy must be atomic.”
- “Happy path next, then unknown ticket, then we can add EV.”
Follow-ups they almost always ask
Add EV spots and a different fee
The lot is full
Two gates at once
Tickets must survive restart
You do not know a library they named
How the six marks sit on the clock
You can hit every mark without filling the whiteboard
Minute five: model and scope spoken. Minute ten: API on the board. Minute twenty: jobs assigned, seam named. Minute thirty-five: happy path plus one illegal case. Minute forty-five: race named, follow-up shown. That is a complete round even if Observer never appears.
People
Actors and two gates named early so the race is not a surprise.
Voice
Every checkpoint is a sentence they can write in notes.
Trade-off
Coarse lock vs per-spot lock; in-memory vs SQL behind TicketBook.
Practice the scorecard, not pattern names
- Take the parking paragraph and speak the six marks in two minutes with no code. If you cannot, the class list is not ready.
- Write only the public API and one invariant. Ask a friend to add EV. Point at the type that changes. If you point at five files, the seam failed.
- Draw two gates and one spot. Say the atomic step. Then write occupy so a second caller cannot succeed.
The rest of the fundamentals loop
- What LLD is asking — splitting responsibilities among objects.
- HLD versus LLD — distributed services versus in-process objects.
- How to run the round — talk, agree the API, then code.
- Requirements to classes — underline nouns and verbs until the Manager is gone.
- Parking lot case — implement the full problem under this scorecard.
Takeaway
They grade the model, the jobs, the API, the gap for change, the two-actor race, and the talking. Pattern names are tools after those answers exist. Narrate as you go. Place every verb. Keep park and exit easy to say. When a new fee rule arrives, add a type — do not rewrite the lot. That is the whole fundamentals loop, from splitting responsibilities to scoring the round.