Why this matters: this is the act where the interview is mostly won or lost. Before any code, an interviewer wants the model in words — which entities exist, what each one owns, and why. Every structural decision here will either localize future change or smear it across the codebase, and an experienced interviewer can tell which from the first two minutes of your explanation.
Start from the operations, not the nouns
A tempting way to start is to list nouns — vehicle, spot, floor, gate, ticket — and give each a class. Nouns are cheap, though. The design pressure comes from the operations:
park(vehicle) -> assign a spot, record the fact, hand back proof unpark(proof) -> compute the charge, free the spot isFull(size)? -> can we even admit this vehicle?
Everything the lot does is one of those three, so the design question becomes: for each operation, which object answers it, and what must be true before and after? That framing produces the model below.
The fit rule: one question, answered in one place
Three vehicle kinds, three spot sizes, and one compatibility rule: a vehicle may take a larger spot than it needs, never a smaller one. The trap — and it's the most common structural mistake in this problem — is to scatter that rule as instanceof checks or switch ladders: one in the entry flow, another inside the assignment loop, a third wherever someone needed it in a hurry. Now the rule exists in three places, and the day it changes, it changes incompletely.
Instead, make sizes an ordered concept and ask the question exactly once:
VehicleSize: MOTORCYCLE < CAR < TRUCK SpotSize: SMALL < MEDIUM < LARGE fits(vehicle, spot) := spot.size >= requiredSize(vehicle)
One comparison, keyed on size ordering, owned by one function. Every other part of the system that cares about fit calls this; nobody re-derives it. That's the single-responsibility principle applied where it actually bites: not "one class does one thing" as a slogan, but one rule has one home, so a rule change is a one-line edit.
The assignment strategy: a policy behind a seam
Our requirement says: nearest available spot to the entry gate, smallest size that fits. Read that sentence again and notice what kind of thing it is — it's a preference, not a law of physics. Operators change preferences: fill upper floors first, spread load evenly, keep the ground floor for quick turnover.
So the design decision is to isolate it:
SpotAssignmentStrategy:
findSpot(vehicle, availability) -> Spot | none
NearestSmallestFit implements SpotAssignmentStrategy
The lot's bookkeeping — which spots exist, which are occupied — stays in the lot. Which free spot to pick lives behind this interface. This is the open/closed principle earned honestly: we're not adding an interface because a pattern catalog said to, we're adding it because we identified the axis most likely to change and arranged for that change to be a new implementation rather than an edit to the lot's internals. If an interviewer asks "why the interface?", that sentence — policy changes shouldn't touch bookkeeping — is the justification that counts. Naming the Strategy pattern without it is decoration.
The ticket: the transactional record
When a vehicle parks, something must remember the association — this vehicle, that spot, since this time. Resist the urge to store it on the vehicle (the lot doesn't own vehicles) or on the spot alone (the spot shouldn't know about billing). The natural home is a Ticket: issued at entry, stamped with the spot and entry time, priced and closed at exit.
The ticket is what makes exit clean: the exit gate operates on the ticket — not on the vehicle, not by searching the lot. Duration comes from the ticket's timestamps; the spot to free is written on it; the charge is computed from what it records. One object carries the whole transaction from entry to exit.
Pricing: data, not surgery
Pricing is keyed to spot size and duration. The mistake is computing it inside the exit flow, or worse, inside Spot. Then a rate change — large spots cost more on weekends, say — is code surgery in a class that has no business knowing about money.
Give pricing its own home: a rate table (or small pricing service) mapping spot size to hourly rate, with the round-up rule in one place. A price change becomes a data edit. This is separation of concerns in its most practical form: the things that change for business reasons live apart from the things that change for engineering reasons.
Availability, and the invariants that keep it honest
One more question needs exactly one answerer: is a spot of size S available? The clean approach is for the lot to maintain availability directly — per-size collections of free spots (the next lesson makes this concrete) — and for every other consumer, including floor-occupancy displays, to derive from that single source rather than keeping their own counters that can drift.
State the invariants out loud; interviewers reward candidates who do:
- a spot is either free or holds exactly one active ticket - every active ticket maps to exactly one occupied spot - availability = the set of free spots; all counts derive from it - park() and unpark() are the only operations that change spot state
What we rejected, and why
Two alternatives deserve explicit burial, because interviewers often ask "did you consider…?"
Floors as arrays-of-arrays. Modeling the lot as spots[floor][row] with index arithmetic leaks coordinates into every method and buys nothing — no operation in our set cares about geometry except assignment, and that's the strategy's business. Floors survive as a spot attribute ("this spot is on floor 2, near gate distance 40m"), not as the data structure everything crawls through.
A god ParkingLot class. The lot could assign spots and price tickets and own the fit rule and format receipts. Every operation would work, and the first requirements change would touch all of them at once. The whole design above is the argument against it: fit rule in one place, policy behind a seam, transaction in the ticket, prices in a table — the lot coordinates, it doesn't do everything. High cohesion, low coupling, demonstrated rather than recited.
Key takeaway
The parking lot's core design is four ownership decisions: the fit rule answered once (single responsibility), assignment isolated as a swappable policy (open/closed, justified by change-anticipation), the ticket as the one transactional record from entry to exit, and pricing as data outside the object model. State the bookkeeping invariants explicitly — one live ticket per occupied spot, all counts derived from spot state — and you've said everything an interviewer needs to hear before code.