← Back to practice catalog

System design interview guide

Parking Lot System Design

TL;DR:

“Parking lot” sounds like a toy until you treat it like a real system: dozens of entry lanes, multiple floors, policies (compact vs large, reserved, accessible, EV), and the one thing you cannot afford to get wrong—never sell the same spot twice. The interview is really about disciplined thinking: define rules precisely, model them cleanly, and then make concurrency and failure behavior explicit. Juniors learn that “classes” are not the goal; your goal is invariants. Seniors get pushed on how those invariants hold under retries, partial failures, and operational realities (devices offline, gates stuck open, humans overriding the system).

Problem statement

Parking lot is a great interview problem because it forces you to be precise. You have a finite resource (spots) and real-world rules (vehicle types, special spots, pricing). The system’s job is not to look “OO”; its job is to maintain a few hard invariants under concurrency:

  • A parking spot can be assigned to at most one active session at a time.
  • A vehicle can have at most one active session at a time (unless you explicitly allow re-entry edge cases).
  • Payment should be idempotent: retries must not double-charge, and exits should not “free a spot twice.”

The trick is to separate policy (which spot should we prefer?) from correctness (how do we guarantee we never double-assign?), and to treat the physical world as an unreliable narrator (sensors can be late, gates can fail, attendants override). If you can tell that story cleanly, juniors learn how to reason from invariants; seniors show they can design for reality, not for perfect inputs.

Introduction

This problem rewards one habit: state your invariants before you draw boxes.

If you start with “I’ll create classes: ParkingLot, Floor, Spot…” you’ll drift into a pretty diagram that doesn’t answer the real question: how does the system stay correct when 3 cars arrive at once and the gate controller retries requests?

For a new engineer, parking lot teaches how to move from requirements → rules → invariants → model → API → concurrency. For a senior engineer, it’s a chance to show operational thinking: the world is messy, events are duplicated, devices go offline, and “available spots” is a product surface that can be approximate—but spot assignment cannot.

How to approach

Use this order in the room.

  1. Lock down the rules (5 minutes).

    • Vehicle types: motorcycle, car, truck.
    • Spot types: compact, regular, large (plus optional accessible/EV).
    • Compatibility: decide it explicitly.
    • Allocation policy: “nearest” (to which entrance?), “any on lowest floor”, or “zone-based”.
    • Payment: on exit, on entry, or mixed? Any grace period?
    • Are reservations allowed? Are attendants allowed to override?
  2. Define the invariants (30 seconds, but do it).

    • Spot has at most one active occupancy.
    • Ticket/session has a single lifecycle (OPEN → PAID → CLOSED).
    • Payment is idempotent and reconciled.
  3. Walk one entry + one exit end-to-end.

    • Entry: allocate spot atomically, create session, return ticket.
    • Exit: compute fee, pay idempotently, release spot once.
  4. Then talk about performance and “real-time” availability.

    • Fast reads with cached counts.
    • Correct allocation with a transactional source of truth.

If you do those four steps, your design will feel intentional.

Interview tips

  • When you say “nearest spot,” define what “nearest” means. Nearest by floor number? By walking distance from a specific entrance? By “same zone”?
  • Make one explicit decision about source of truth: availability counts can be cached, but spot occupancy state must be durable.
  • When the interviewer asks about concurrency, do not say “we use a lock” generically. Say what is locked (spot row, floor pool, or “active session by vehicle id”) and how retries behave (idempotency key).
  • Pricing is a trap: define rounding (per minute? hourly?), grace periods, and “lost ticket” handling.

Capacity estimation

Given the prompt numbers:

InputEstimateWhy it matters
Vehicles per day~2000Writes are moderate; correctness beats throughput.
Peak arrival rate~5/minConcurrency exists, but not “internet scale.” Still enough to double-assign without atomicity.
Spots~1000A single database can handle it; the design focus is modeling + invariants.
Availability readsHighDisplays + apps read often; caching counts matters more than optimizing entry QPS.

Implication: Your backend can be simple and still be impressive—if the invariants and lifecycle are crisp.

High-level architecture

The physical world produces events; your backend enforces state transitions.

[ Gate / Sensor ] → [ Entry/Exit API ] → [ Parking service ] → [ DB (truth) ]
                           |                    |
                           |                    → [ Cache / counters (fast reads) ]
                           |
                           → [ Payments provider ] → [ Payment records ]

Key split: allocation happens against the DB in a transaction; availability can be served from counters that are eventually consistent.

Data model (entities + invariants)

You don’t need a giant ERD—just enough to encode correctness.

Core entities

  • ParkingLot: metadata (name, address), configuration (policies, pricing rules).
  • Floor: id, level number, zones.
  • Spot: spot_id, floor_id, type (compact/regular/large), status (AVAILABLE/OCCUPIED/OUT_OF_SERVICE), optional features (EV, accessible).
  • Vehicle: vehicle_id (or plate), type.
  • ParkingSession (ticket): session_id, vehicle_id, spot_id, entry_time, exit_time?, status (OPEN/PAID/CLOSED), created_by (gate vs attendant), version.
  • Payment: payment_id, session_id, amount, currency, status, provider refs, idempotency_key.

Invariants to enforce

  1. One active session per spot.
  2. One active session per vehicle (optional, but usually intended).
  3. Session lifecycle is monotonic: OPEN → PAID → CLOSED (no skipping backwards).
  4. Payment idempotency: the same “pay” request cannot double-charge.

The rest of the “OO design” flows naturally once these are clear.

Detailed design

Compatibility rules (make them explicit)

One simple policy:

  • Motorcycle → any spot (compact/regular/large)
  • Car → regular or large
  • Truck → large only

Then layer policy rules:

  • Accessible spots require driverHasPermit=true
  • EV spots require vehicle.isEV=true (or allow non-EV at high occupancy with an explicit policy)

The key is separating physical fit from policy eligibility.

Entry flow (allocate spot)

Goal: allocate one compatible spot atomically and return a ticket.

  1. Request: vehicle type + optional preferences (floor, zone, accessible, EV).
  2. Parking service selects candidate set (by policy).
  3. Atomically claim one spot and create a session.
  4. Return { session_id, spot_id, floor, instructions }.

Atomic claim can be done in multiple ways; pick one and defend it.

Option A: transaction on spot row (simple and strong).

  • Query candidate spot ids (bounded).
  • SELECT spot WHERE status=AVAILABLE ... FOR UPDATE SKIP LOCKED (or equivalent).
  • Update spot to OCCUPIED.
  • Insert ParkingSession with status OPEN.

Option B: unique constraint on active occupancy.

  • Insert session row with (spot_id, status=OPEN) where a partial unique index enforces only one OPEN session per spot.
  • If conflict, retry with another spot.

For this scale, Option A is easy to reason about.

Exit + payment flow

Goal: compute fee, charge once, release spot once.

  1. Exit request arrives with session_id (ticket) or plate lookup (lost ticket flow).
  2. Compute fee from entry_time → now (apply grace/rounding).
  3. Create/confirm a Payment record using an idempotency key:
    • Key could be pay:{session_id}:{amount}:{pricing_version}.
  4. If payment succeeds, transition session to PAID, then CLOSED.
  5. Release spot (OCCUPIED → AVAILABLE) exactly once.

Important: don’t release the spot before confirming payment unless you explicitly allow “pay later” with an attendant override.

API design

Keep the surface small and state-driven.

  • POST /v1/entry → allocate a spot, create session
  • POST /v1/exit → compute fee, start payment (or return amount + payment token)
  • POST /v1/payments/confirm → confirm payment for a session (idempotent)
  • GET /v1/availability?floorId=&spotType= → counts + optional breakdown
  • POST /v1/spots/{spotId}/override → attendant sets OUT_OF_SERVICE / forces AVAILABLE (audited)

API flow sketch:

Client/Gate → POST /entry
  → allocateSpot(txn) → createSession(OPEN) → return ticket

Client/Gate → POST /exit
  → computeFee → createPayment(idempotent) → confirm → closeSession → releaseSpot

Concurrency: the “two cars, one spot” problem

This is where seniors separate from templates.

What can go wrong

  • Two entry lanes both see “Spot 12 is free” and both assign it.
  • Gate controller times out and retries /entry, creating two sessions.
  • Exit is processed twice (double release) or payment is confirmed twice (double charge).

Techniques that actually work

  • Row-level locking on the chosen spot (transaction).
  • Optimistic concurrency with a version field on Spot / Session.
  • Idempotency keys on entry and payment confirmation:
    • entry:{gate_id}:{request_id}
    • pay:{session_id}:{amount}
  • Uniqueness constraints:
    • One OPEN session per spot_id.
    • One OPEN session per vehicle_id.

A great answer says which combination you’ll use and why.

Failure handling (real world, not perfect world)

Devices offline or duplicated events

Treat gate/sensor events as at-least-once. Your backend must:

  • Deduplicate by idempotency key.
  • Allow reconciliation: if a sensor says “occupied” but DB says available, flag for attendant review.

“Lost ticket” / missing correlation

Make it explicit:

  • If user lost the ticket, you can locate active session by plate.
  • If you can’t prove entry time, charge a max-day rate and record a “manual override” reason.

Overrides (humans exist)

Add an audited override API:

  • Spot OUT_OF_SERVICE.
  • Force release with reason + actor identity.
  • Create session manually.

The insight is that overrides are not “edge cases”; they are how systems survive reality.

Production angles

This section is where a senior engineer adds value beyond the basic model.

1) “Availability says 20 free, but drivers can’t find spots”

What it looks like: entry displays show availability, but the lot feels full; attendants get complaints; spot-by-spot reality disagrees with counts.

Why it happens: counts are cached/aggregated, sensors miss updates, or override workflows don’t emit correct state transitions. Your “available” definition is too naive (e.g., OUT_OF_SERVICE still counted).

What good teams do:

  • Define availability as a function of spot state machine (AVAILABLE, OCCUPIED, RESERVED, OUT_OF_SERVICE).
  • Rebuild counters periodically from truth (nightly or hourly reconciliation job).
  • Alert on divergence: abs(derived_count - cached_count) > threshold.

2) Gate retries create duplicate sessions

What it looks like: one vehicle gets two tickets; billing disputes; occupancy appears inflated.

Why it happens: network timeouts cause client retries; backend is not idempotent on entry.

What good teams do:

  • Require Idempotency-Key on /entry.
  • Store a request log keyed by idempotency key → session_id.
  • Make entry safe to retry and safe under partial failure (client didn’t receive response).

3) Double-charging on payment confirmation

What it looks like: user is charged twice; support burden is high; reconciliation is manual.

Why it happens: payment providers often recommend retrying confirmations; your system treats “confirm” as a new charge.

What good teams do:

  • Create a PaymentIntent object and confirm it idempotently.
  • Store provider reference id and enforce uniqueness.
  • Release the spot only after the payment is in a terminal success state (or explicitly allow debt/override).

4) Clock skew breaks pricing

What it looks like: negative durations, weird fees around DST changes, disputes.

Why it happens: entry time taken from device clocks; timezone ambiguity.

What good teams do:

  • Use server time (UTC) for all session timestamps.
  • Treat device time as advisory metadata only.

How to use this in an interview

Pick one production angle and narrate it like a postmortem:

  • Symptom: “counts diverge” or “duplicate tickets.”
  • Root cause: “cached counters + missing idempotency.”
  • Fix: “state machine + reconciliation + idempotency keys + constraints.”

That’s the difference between “I can draw a class diagram” and “I can ship systems.”

Core challenges

  • The system is defined by invariants (“a spot can be occupied by at most one vehicle”)—the hard part is enforcing them under concurrency and retries.
  • Policies create edge cases: spot compatibility, reservations, accessible/EV spots, overflow rules, and “nearest spot” semantics.
  • State changes are triggered by the physical world (sensors, gates, attendants), which is lossy and inconsistent; your model must handle missing/late events.
  • Pricing looks simple until you handle grace periods, lost tickets, clock skew, and idempotent “pay” operations.
  • Real-time availability is a read-heavy surface; caching and correctness must be balanced without showing wildly wrong counts.

Frequently asked follow-ups

  • How do you ensure two cars don’t get the same spot at the same time?
  • How do you model different vehicle sizes vs spot sizes?
  • How do you compute price and handle payment retries without double-charging?
  • How do you handle “lost ticket” or an entry event with no exit (or vice versa)?
  • What do you cache, and what’s the source of truth for availability?

Deep-dive questions and strong answer outlines

Walk through what happens when a car arrives at an entry gate.

Validate vehicle type, choose an assignment policy, allocate a compatible spot atomically, create a parking session/ticket with a unique id, and return the spot + ticket. Mention that gate hardware should not be the source of truth; treat “gate opened” as an event, and make allocation idempotent by request id/ticket id.

How do you prevent double allocation under concurrency?

Use a DB-enforced invariant: either a unique constraint on spot_id in the active session table, or a spots row with status=AVAILABLE|OCCUPIED transitioned via transaction/row lock. Candidates mention “SELECT ... FOR UPDATE”, optimistic version checks, or atomic compare-and-swap in a KV—plus idempotency keys for retries.

How do you handle exit and payment, especially retries?

Compute fee from session start/end (with rounding/grace rules), create a payment intent with an idempotency key, finalize payment, then release the spot. The release should be idempotent and only happen once payment is confirmed (or allow a “pay later” flow with an attendant override, but make it explicit).

How do you model vehicle vs spot compatibility?

Encode compatibility rules (e.g. motorcycle fits any, car fits regular/large, truck fits large only) and treat “special” spots (accessible/EV) as separate classes with policy gates. Strong answers separate “spot physical size” from “policy eligibility.”

AI feedback on your design

After a practice session, InterviewCrafted summarizes strengths, gaps, and interviewer-style expectations—similar to a written debrief. See a static example report, then practice this problem to get feedback on your own answer.

FAQs

Q: Is this an LLD (OOP) question or a systems question?

A: It’s both. Interviewers use it to see whether you can model a domain cleanly (classes/entities) and enforce invariants under concurrency (transactions, idempotency, retries).

Q: Do I need sensors and hardware details?

A: Not in depth. It’s enough to treat them as external systems emitting events that can be delayed or duplicated. The key is: your backend must be correct even when devices are flaky.

Q: Can I just keep availability in memory?

A: Only as a cache. The source of truth must be durable (DB / KV). In-memory is fine for speed, but your design must explain how it’s rebuilt and kept consistent.

Q: What’s the common mistake candidates make here?

A: They jump to class diagrams without defining rules (compatibility, reservations, pricing) and without a concurrency story. The fastest way to fail is to say “we pick any free spot” with no atomicity.

Practice interactively

Open the practice session to use the canvas and stages, then review AI feedback.