Design a Parking Lot System
Step-by-step guide to the parking lot system design interview question: requirements, entity model, concurrent spot allocation across multiple gates without double-assignment, pricing and exit flow, and handling payment outages gracefully.
Why interviewers ask this question
A parking lot system runs an automated garage: cameras read license plates at the gates, the system assigns each vehicle a free spot of the right size, and it computes a fee when the vehicle leaves. There is no web-scale traffic here — and that is the point. Interviewers use this question to see whether you can model a real-world domain cleanly (spots, vehicles, tickets, rates) and whether you understand concurrency at small scale: six gates admitting vehicles at the same time must never assign the same spot twice.
It is a classic first system design question and a favorite for testing object-oriented modeling. Beginners often treat it as a pure class-diagram exercise; the stronger answer also covers the two operational realities in the prompt — the allocation race between gates, and keeping gates moving when the payment provider is down.
The 30-second answer
Step 1 — Requirements
The facility: 4 levels, ~2,000 spots across car, motorcycle, and truck sizes, with camera-based plate recognition at 6 entry/exit gates. Peak flow is ~10 vehicles/minute per gate at rush hour.
Functional requirements
- Admit a vehicle and assign the nearest free spot matching its size.
- Track entry and exit times per vehicle via plate recognition.
- Compute parking fees from duration and vehicle type on exit.
- Show real-time space availability per level at gates and online.
- Let operators manage pricing, close levels, and view occupancy analytics.
Non-functional requirements
- Gate decision (spot assignment) in under 2 seconds per vehicle — a slow gate backs up traffic onto the street.
- No double-allocation of a spot, even with 6 gates operating concurrently.
- Gates keep admitting vehicles (degraded mode) if payments are down for up to 10 minutes.
- Occupancy counts accurate within 1 spot at all times.
Step 2 — Scale check: this is a correctness problem, not a scale problem
Run the numbers before reaching for distributed-systems machinery:
- Peak entry rate: 6 gates × 10 vehicles/min = 60 vehicles/min = 1 allocation per second. Off-peak it is far less.
- Data volume: 2,000 spot rows, a few thousand parking sessions per day. Years of history fit in a few gigabytes.
- Read load: occupancy displays refresh every few seconds — trivial.
One modest relational database handles this with three orders of magnitude of headroom. The design challenge is not "how do I scale?" but "how do I make one operation — claim a spot — atomic across concurrent gates, and how do I keep the gates open when a dependency fails?"
Why say this out loud?
Step 3 — Entities and data model
Four core entities cover the domain:
- Spot — one row per physical spot: level, spot number, size class, and current status. This row is the unit of contention.
- Vehicle — identified by license plate (the cameras give you this for free); carries the size class used for matching and pricing.
- ParkingSession (the "ticket") — one row per visit: plate, assigned spot, entry time, exit time, computed fee, payment status.
- RatePlan — per vehicle type and per level, editable by operators.
Spot sizes nest: a motorcycle fits in a car spot, a car fits in a truck spot — but not the reverse. Encode this as an ordered size class and allocate the smallest class that fits, so trucks are not blocked by cars sitting in the only large spots.
CREATE TABLE spots (
id BIGINT PRIMARY KEY,
level INT NOT NULL, -- 1..4
spot_number INT NOT NULL,
size SMALLINT NOT NULL, -- 1=motorcycle, 2=car, 3=truck
status TEXT NOT NULL DEFAULT 'FREE', -- FREE | OCCUPIED | CLOSED
distance_rank INT NOT NULL -- precomputed "nearness" to gates
);
CREATE TABLE parking_sessions (
id BIGINT PRIMARY KEY,
plate TEXT NOT NULL,
vehicle_size SMALLINT NOT NULL,
spot_id BIGINT REFERENCES spots(id),
entered_at TIMESTAMP NOT NULL DEFAULT now(),
exited_at TIMESTAMP,
fee_cents INT,
payment_status TEXT NOT NULL DEFAULT 'PENDING' -- PENDING | PAID | OWED
);
CREATE INDEX idx_spots_free
ON spots (size, distance_rank) WHERE status = 'FREE';"Nearest suitable spot" needs a definition of near. Do not compute geometry at gate time — precompute a distance_rank per spot (per gate, if gates differ meaningfully) when the lot is configured. Allocation then becomes "lowest-ranked FREE spot of a fitting size", which is a single indexed query. The partial index above makes that lookup fast and keeps it fast as the lot fills.
Step 4 — Concurrent spot allocation (the core of the interview)
Here is the race that decides this interview. The naive gate logic is:
SELECTthe nearest free spot for this vehicle size.UPDATEit to OCCUPIED and create the session.
With one gate this works. With six gates, two vehicles arriving in the same second can both run step 1, see the same free spot, and both "win" step 2 — two cars sent to one spot. This is a textbook read-then-write race, and fixing it is the whole point of the question.
Three standard fixes, in increasing order of ceremony:
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| Atomic conditional claim (recommended) | One statement: UPDATE the best spot only if it is still FREE; row locking makes concurrent claimers take different rows | Correct by construction, one round-trip, easily meets the 2-second gate budget | Ties allocation logic to the database; needs care with lock waits under contention |
| Pessimistic lock (SELECT ... FOR UPDATE) | Lock the candidate row inside a transaction, then update it | Simple to reason about; standard SQL | A second gate blocks on the same row; use SKIP LOCKED so it takes the next spot instead of waiting |
| Single allocator process / queue | All 6 gates send requests to one serialized allocator (in-process or via a queue) | No DB-level concurrency at all; trivial to reason about | The allocator is a single point of failure and adds a component — overkill at 1 request/sec, but a fine mention |
UPDATE spots
SET status = 'OCCUPIED'
WHERE id = (
SELECT id FROM spots
WHERE status = 'FREE'
AND size >= :vehicle_size -- smallest fitting class first
ORDER BY size ASC, distance_rank ASC
LIMIT 1
FOR UPDATE SKIP LOCKED -- concurrent gates skip contested rows
)
RETURNING id, level, spot_number;Walk through why this is safe: the inner SELECT ... FOR UPDATE SKIP LOCKED locks the chosen row, and any other gate running the same query at the same instant skips that locked row and picks the next-nearest spot. Both vehicles get valid, distinct spots in one round-trip each. If the query returns no row, the lot (or that size class) is full — reject at the gate and show "FULL" on the display.
Two details worth mentioning unprompted:
- Idempotency: cameras can double-fire on one vehicle. Key the session on
(plate, open session)— if a plate already has an open session, return the existing assignment instead of allocating a second spot. - Closed levels: operators can close a level; add
AND level NOT IN (SELECT ...closed levels)or fold aCLOSEDstatus into the spot rows so allocation respects it automatically.
Step 5 — Exit flow, pricing, and surviving a payment outage
The exit flow mirrors entry: the camera reads the plate, the system finds the open session, computes the fee, takes payment, frees the spot, and opens the gate.
Pricing is a function of duration and vehicle type — fee = rate(vehicle_type, level) × billable_hours, with common wrinkles like a grace period (first 15 minutes free) and a daily cap. Keep rates in the RatePlan table so operators can change them without a deploy; snapshot the rate onto the session at entry so a mid-stay price change does not surprise the driver.
Freeing the spot and closing the session must be atomic — one transaction sets exited_at, writes the fee, and flips the spot back to FREE. If these were separate writes and the second failed, you would leak a spot (marked occupied forever) or double-free it.
Now the requirement that separates good answers: gates keep admitting vehicles if payments are down for up to 10 minutes. The insight is that payment is not actually required to operate a gate — the plate is the identity, and the debt can be collected later.
- Exit during outage: compute the fee, mark the session
OWEDinstead ofPAID, open the gate. Retry the charge in the background; if the driver returns, settle the balance on the next visit. - Entry during outage: entry never touches the payment provider at all — allocation is purely internal, so entries are unaffected by design. Point this out; it shows you decomposed the dependencies.
- Queue failed charges durably (a simple
pending_chargestable works) and process them when the provider recovers.
Tradeoff: Fail-open gates during payment outages
- Traffic keeps flowing — a blocked exit lane at rush hour is a physical-world incident, not just an error rate
- Plate recognition means identity survives the outage; revenue is deferred, not lost
- Decouples the gate-decision path (must be fast and available) from the payment path (can be async)
- Some fees become uncollectable if a driver never returns — an explicit, bounded revenue risk
- Needs a reconciliation job and an OWED state, adding a little lifecycle complexity
- Requires a business sign-off: engineering alone cannot decide to let cars leave without paying
Step 6 — Occupancy counts and the operator dashboard
The requirement is occupancy accurate within 1 spot at all times. There are two ways to produce a per-level count:
- Derive it:
SELECT level, count() FROM spots WHERE status = 'FREE' GROUP BY level. Always consistent with the source of truth, because it is* the source of truth. At 2,000 rows this query is effectively free — cache it for a second if you like. - Maintain counters: increment/decrement a per-level counter on every entry/exit. Faster in theory, but every code path that touches a spot must also touch the counter, and any missed update makes the count silently drift.
At this scale, derive it. Counters are the right tool when counting rows is genuinely expensive (millions of rows, thousands of updates/sec) — earning the "accurate within 1 spot" guarantee for free by deriving from spot state is the better trade here.
For gate displays and the online availability view, poll the derived count every 1–2 seconds or push updates over a WebSocket/SSE channel; either comfortably meets a "real-time" bar for parking. The operator dashboard reads the same data plus session history for analytics (peak hours, average stay, revenue per level).
Common mistakes that cost offers
- Class diagrams only. Modeling
Vehicle,Spot, and inheritance for 20 minutes without ever addressing the six-gate allocation race — the concurrency story is the question. - Check-then-act allocation. Presenting SELECT-then-UPDATE as the answer. If the interviewer asks "what if two gates do this at once?" and you have to patch it live, you have lost the core point.
- Over-engineering. Sharded databases, Kafka, and a microservice per entity for 1 write/sec. Match the design to the numbers you stated in step 2.
- Coupling gates to payments. If your exit gate cannot open without a successful charge, the 10-minute-outage requirement fails. Payment is deferrable; gate movement is not.
- Forgetting spot-size nesting. Assigning a motorcycle to the last truck spot, then turning away a truck. Allocate smallest-fitting-class first.
- No idempotency at the gate. Cameras re-fire; without an open-session check, one car consumes two spots.
Frequently asked questions
Is the parking lot question a system design or object-oriented design question?
It is asked both ways. As an OOD question the focus is classes and state machines; as a system design question the focus shifts to the data model, the concurrent spot-allocation race across multiple gates, and operational concerns like payment outages. A strong answer covers the entity model briefly and spends most of the time on allocation correctness and the exit flow.
How do you prevent two gates from assigning the same parking spot?
Make the claim atomic. In a relational database, use a single conditional UPDATE that selects the nearest free spot with FOR UPDATE SKIP LOCKED, so concurrent gates lock different rows and each receives a distinct spot. Alternatives are a pessimistic transaction that locks the candidate row, or serializing all allocations through a single allocator process.
Do you need a distributed system for a parking lot?
No. Six gates at ten vehicles per minute each is about one allocation per second, and two thousand spots is a tiny dataset. A single relational database with an atomic claim query meets the two-second gate budget with large headroom. The interview rewards recognizing this and focusing on correctness instead of scale.
What happens if the payment system goes down?
Gates fail open. Entry never depends on payments, and on exit the system computes the fee, marks the session as owed, opens the gate, and retries the charge in the background. License plate recognition preserves identity, so the debt can be settled later or on the next visit. Blocking exits during an outage creates a physical traffic incident, which is worse than deferred revenue.
Reading only gets you halfway
Practice designing a Parking Lot System step by step with an AI interviewer that evaluates your answers — free, no credit card.
Practice this problem free