Cruxly

Cruxly › Low-level design › Running the LLD Interview

From Nouns to a Defensible Model

entities, ownership, invariants — in that order

Open the interactive lesson

Prompt: you have a clarified scope for a parking lot: multiple levels, three vehicle sizes, ticket on entry, hourly fee on exit, multiple concurrent gates.

Produce the class model. Be ready to defend every class you name — and every one you didn't.

Nouns first, then aggressive cutting

List every noun the clarified prompt contains: lot, level, spot, vehicle, size, ticket, entry, exit, fee, rate, gate.

Now cut, with a reason for each cut you can say out loud.

What survives: Lot, Level, Spot, Vehicle, Ticket, FeeStrategy. Six. Every one of them earns its place, and you can say why in a sentence each.

Cutting out loud is the valuable part. A candidate who lists eleven classes and a candidate who lists six and explains five cuts have produced very different signals from the same starting list.

Ownership and the shape of the model

Ownership is what stops a model from becoming a bag of mutually-referential objects.

OWNS — solid, exactly one parent Lot 1 → * Level 1 → * Spot currentTicketId? REFERENCES — dashed, one direction only Ticket Vehicle Spot does NOT hold a list of tickets it would grow forever, and you'd still need an index State lives on the entity. History lives in a log.
Solid diamonds are ownership; dashed arrows are references. The crossed line is the relationship you deliberately chose not to model — and saying why is the point.

:::key Arrow direction is a design decision, so state it Ticket points at Spot. Spot does not hold a list of tickets.

If Spot held its history, every spot would grow without bound — and lookups by ticket would still need a separate index. So the reference goes one way, and a Map<TicketId, Ticket> on the lot handles lookup. :::

A Spot does need to know whether it is currently occupied — but that is one nullable field (currentTicketId), not a collection. That distinction is the whole difference between modelling state and modelling history.

Invariants — the actual design

Write these before any method signature:

#InvariantWhy it exists
1A spot holds at most one open ticketThe core one — assignment exists purely to protect it
2A ticket is open or closed, never both, never reopensMakes the state machine trivially auditable
3A closed ticket's fee is immutableRecomputing a fee after the fact is how billing disputes are born
4A vehicle has at most one open ticket in a lotPrevents double-entry; needs an index to enforce cheaply

Now the methods fall out, and each one exists to protect a numbered invariant:

Ticket admit(Vehicle v);       // protects 1 and 4
Fee    release(TicketId t);    // protects 2 and 3

:::key Two public methods is the finding, not an accident A design that reduces to a small public surface over well-named invariants is far easier to defend than one with fifteen methods and no stated rules.

It is the same design either way. The difference is describing it in the order that shows you understood why it has that shape. :::

Where the interfaces go, and where they don't

The scope said pricing varies, so pricing gets a seam:

interface FeeStrategy { Fee compute(Ticket t, Instant exitTime); }
interface FitPolicy   { boolean fits(VehicleSize v, SpotSize s); }

HourlyFee is the first implementation. When the interviewer says "now weekends are double," you add an implementation instead of editing a method — and you say that out loud, because it is the entire reason the seam exists.

FitPolicy is the second candidate: can a motorcycle take a large spot? That rule will change, so it gets a seam too.

:::warn The most common over-engineering tell in an LLD round Does anything else get an interface? No.

Ticket has one implementation and no expected second. Level likewise. Adding interfaces "for testability" on entities with no variation is the classic tell — so say explicitly that you are not doing it, and why. The restraint is the signal. :::

:::warn And no ParkingLotManager If a class named Manager or Service accumulates all the behaviour, your entities have become structs and you have written a procedure with extra steps.

Lot.admit() belongs on Lot — because Lot owns the levels it searches. :::

The concurrency story

Multiple gates means admit is contended and release mostly is not.

The naive answer — one lock on Lot — serialises every gate in the building. The interviewer will push on it, so get ahead of it with a lock per level: two cars entering different levels never contend, and admit holds the level lock only while scanning for and claiming a spot, releasing it before creating the ticket.

THREAD A THREAD B findFree(size) findFree(size) spot 7 free both see it as free A: occupy(t1) B: occupy(t2) invariant 1 violated two tickets, one spot The gap between finding and claiming is the entire bug.
Find-and-claim as two steps is the classic LLD concurrency failure — and the same shape as the atomic-conditional-update pattern used throughout the design track.
Spot s = level.findFree(size);   // A and B both see spot 7
s.occupy(ticket);                // both write — invariant 1 is gone
// one critical section: find and claim cannot be separated
synchronized (level) {
    Spot s = level.findFree(size);
    if (s != null) s.occupy(ticket);
}
// ...or make occupy() a compare-and-set that can fail and re-search

:::key What's actually being graded here Either fix is acceptable — one critical section, or a CAS that can fail and send you back to searching. Saying which one you chose, and why, is the part being assessed.

release needs no lock beyond the spot itself: it touches one spot and one ticket, and the caller already uniquely owns that ticket. :::

:::note If they told you it's single-threaded Don't write locks. Say: "Single-threaded, so no locks. If this became multi-gate, the contended path is find-and-claim in admit, and I'd lock per level."

Same understanding, one sentence, no wasted code. :::

How to say it in the interview

"I model ownership first: a Lot owns Levels, a Level owns Spots. Then the invariant — a spot has at most one open ticket — because that single sentence is what every method is protecting."

Checklist

Practice this interactively
Share this lessonWhatsAppLinkedInX

Related lessons