← Back to practice catalog

System design interview guide

Parking Lot System Design

TL;DR:

Saturday mall rush: entry gates disagree with payment kiosks on whether level 3 is full because spot counts updated eventually. Two cars can both be told to park in the same space unless assignment is atomic. The interview still needs concurrency on spots, a clear ticket lifecycle, and idempotent payment—at scale you add multi-entry coordination and reconciliation between displays and truth.

Overview diagram for Parking Lot System Design

Problem statement

Design a parking lot system that assigns compatible spots on entry, tracks each stay as a ticket with a clear lifecycle, charges on exit without double billing, and shows availability that is fast for drivers but honest enough that two cars never park in the same space.

Introduction

Two cars pull into the same garage at the same moment. Both entry screens flash “Spot 12 — proceed.” One driver parks. The other finds a sedan already in the space. At the exit lane, a gate times out, retries payment, and the card gets charged twice.

That is what users feel when the backend treats parking like a class diagram instead of a state machine with rules.

Interviewers are not grading UML. They want to hear what must never happen—two cars, one spot; two charges, one exit—and how your design survives retries, offline gates, and humans overriding the system when sensors lie.

Weak answers start with ParkingLot, Floor, and Spot classes and stop. Strong answers start with invariants: at most one active occupancy per spot; one ticket lifecycle; payment that survives duplicate confirms. Then they draw boxes that enforce those rules.

Parking feels “low level” because the entities are familiar. The system-design depth is the same pattern as booking the last vaccine slot or selling the last concert seat: many readers, one writer, one winner. The difference is pace—five arrivals per minute at a mall garage, not a million refreshes per second—but the concurrency mistake is identical: read “free,” decide later, write, and hope.

If you remember one thing: State your invariants before you draw boxes. Spot assignment must be exact; availability counts can be approximate for displays.

How to approach

Start the way a driver experiences the garage—not by listing databases. Spend the first few minutes turning a vague prompt into a contract. Talk through clarifying questions out loud, then walk one entry and one exit end to end before you argue about caching.

Clarifying questions (say these in the room)

You: “Which vehicle types must we support—motorcycle, car, truck—and which spot types exist: compact, regular, large, plus optional EV or accessible?”

Interviewer: “Motorcycle, car, truck; spots are compact, regular, large; a few EV and accessible spots.”

That answer forces you to separate physical fit (car cannot use compact) from policy (EV spot reserved for EV unless you declare overflow rules).

You: “When we assign ‘nearest available,’ nearest to what—the main entrance, a zone the driver selects, or lowest floor first?”

Interviewer: “Define a policy; consistency matters more than the exact heuristic.”

Now you can name a rule—same zone, then lowest floor, then lowest spot number—and say allocation still atomically claims one row; “nearest” only narrows the candidate set.

You: “Is payment on exit only, or prepay on entry? Any grace period before hourly billing starts?”

Interviewer: “Exit payment with a short grace period is fine.”

That locks the exit flow: compute fee at leave, idempotent pay, then release spot.

You: “Do we need reservations, or walk-in only? Can attendants override assignment or mark spots out of service?”

Interviewer: “Walk-in is required; reservations optional. Attendants can override.”

Overrides become first-class APIs with audit—not a footnote.

You: “I want to leave fleet-wide navigation routing, a full payment processor implementation, and license-plate OCR accuracy out of scope so we can focus on spot contention, tickets, and payment idempotency. Does that match what you want?”

Interviewer: “Yes—focus on assignment and entry/exit.”

Saying out of scope early keeps the round on the hard part.

Minute pacing and order in the room

PhaseTimeWhat to cover
Rules and invariants~5 minVehicle/spot types, allocation policy, payment timing, reservations
One entry story~8 minAtomic claim, OPEN session, idempotency key, ticket returned
One exit story~8 minFee rules, idempotent pay, PAID → CLOSED, release spot once
Availability and displays~5 minCached counts vs DB truth, reconciliation
Scale and failures~remainingMulti-lot sharding, sensor drift, lost ticket
  1. Clarify scope (types, policy, payment, reservations, overrides).
  2. State invariants out loud—one spot, one lifecycle, idempotent pay.
  3. Walk entry: claim spot in one transaction, create OPEN session.
  4. Walk exit: fee, pay once, release spot once.
  5. Explain how display boards can lag briefly but assignment never lies.
  6. Name failure modes: gate retry, lost ticket, sensor mismatch.

In the room (a fuller opening you can actually say): “I will lock vehicle and spot rules first, then state invariants: one active session per spot, monotonic ticket lifecycle, idempotent payment. Entry atomically claims one compatible spot with a row lock and returns a ticket; gate retries use an Idempotency-Key so we do not issue two sessions. Exit computes fee from server UTC time, charges once, then releases the spot exactly once. Availability signs read cached counts rebuilt from spot state—the database owns who is in spot 12.”

Out of scope (and why you park these)

Turn-by-turn navigation inside the garage. Finding spot 214 on a map is a product feature; the interview depth is claiming spot 214 exactly once.

Building the card network or PCI vault. You need idempotent payment confirmation; you do not need to design Stripe in the round.

Perfect real-time computer vision on every bay. Plate recognition may exist at the gate; the design assumes you get a vehicle id or plate string and focus on session correlation and lost-ticket policy.

City-wide traffic routing between lots. Multi-lot search can be a read aggregator; assignment writes stay per lot_id.

Capacity estimation

Before you draw architecture boxes, use rough numbers so you do not promise internet-scale sharding for a single mall garage—or under-design concurrency because “it is only parking.”

InputEstimateWhy it matters
Vehicles per day~2,000Moderate write rate; correctness beats raw throughput.
Peak arrival rate~5/min across all entry lanesLow compared to flash sales—but enough to double-assign without atomicity.
Spots per lot~1,000One primary database per lot is realistic; focus is modeling + invariants.
Availability readsHigh (signs, apps, kiosks)Reads dominate; cache counts, not claims.
Exit payment burstsEvening peak ~10/minIdempotent payment matters more than sub-millisecond fee math.
Multi-lot operator50–500 lotsShard by lot_id; no single row for “all spots in the city.”

Three practical lessons:

  • Correctness beats throughput for assignment—a single double-booked spot destroys trust faster than a slow display board.
  • Counters are cheap; claims are precious—optimize sign refresh and app polls, not the transactional entry path.
  • Gate retry rate may be 1–3% on flaky networks—design entry and payment for duplicate requests from day one.

If you remember one thing: You do not need national-scale queueing; you do need atomic allocation when two lanes pick the same free spot.

High-level architecture

What breaks if you merge everything

One API that assigns a spot, charges a card, updates LED signs, emails a receipt, and writes analytics—all synchronously at the entry gate—means the driver waits on the slowest step. A payment timeout should not block the next car from entering.

The design also fails if each entry lane keeps its own list of free spots in memory. Lane A and Lane B can both believe Spot 12 is free. Caching “20 regular spots left” in Redis without a reconciliation path to spot rows produces angry drivers who cannot find an open bay.

What works: separate claim, display, and payment

Break responsibilities clearly:

Entry/Exit API — Gate-facing endpoints with idempotency keys; validates vehicle type and policy; orchestrates claim and release.

Parking service — Owns spot state machine, session lifecycle, compatibility rules, and fee calculation policy.

Authoritative database — Source of truth for spot occupancy and open sessions; allocation runs here in a transaction.

Availability cache — Derived counters per floor and spot type for fast signs; rebuilt from spot rows; may lag seconds.

Payment service — Creates payment intents, confirms with provider, stores idempotent payment records; does not own spot rows.

Attendant / admin API — Audited overrides (OUT_OF_SERVICE, force release, manual session).

[ Entry/Exit Gate ] --> [ Entry/Exit API ] --> [ Parking service ] --> [ DB (truth) ]
        |                      |                      |
        |                      |                      +--> [ Cache: free counts ]
        |                      |
        +--> [ Sensor events ]  +--> [ Payment provider ] --> [ Payment records ]
                                          (async confirm OK)

In the room: Draw the split—database owns “who is in spot 12”; cache owns “how many regular spots left on floor 2.” Entry always claims on the database; signs may read cache.

If you remember one thing: Assign against truth; display from cache with reconciliation.

Core design approaches

Interviewers want comparisons, not one diagram and silence.

Spot selection policies

PolicyPlain ideaGood whenWatch out
Nearest to entranceMinimize walking from a fixed gateSingle main entrance, simple UX“Nearest” undefined → ask which entrance
Lowest floor firstFill lower levels before upperStructures where upper levels are premiumMay cluster traffic on one floor
Zone-basedDriver picks zone A/B/C; assign inside zoneLarge airports or stadiumsEmpty zones while others full—need overflow rule
Type pool only“Any regular spot” without naming id until ticketFast gates, simple signsDriver needs clear directions after claim

Whatever policy you pick, selection chooses candidates; one transaction claims exactly one row. Policy does not replace locking.

Single lot vs multi-lot

ModelPlain ideaGood whenWatch out
Single lot serviceOne database partition per garageMall, airport terminalHot rows only within that lot
Multi-lot operatorMany lots, shared billing/reportingCity operator, chain garagesDo not centralize all spot rows on one shard
Federated searchRead aggregator across lotsApp shows “parking near me”Entry/exit writes still local to owning lot

Interview land: “Shard inventory by lot_id. Cross-lot browse is a read path; claim is always on the lot that owns the spot.”

Reservation vs walk-in

ModelPlain ideaGood whenWatch out
Walk-in onlyClaim AVAILABLE spot at entrySimple interview scopeEvening rush races on last spots
Hold then arriveRESERVED with TTL until check-inEV or accessible spots scarceExpired holds need cleanup job
Mixed poolsReserved pool + walk-in poolOperators need bothPolicy for stealing reserved spots at high occupancy

For walk-in contention, use the same atomic claim as vaccine last-slot booking: one transaction, one winner. For reservations, transition RESERVED → OCCUPIED on check-in with the same lock pattern.

If you remember one thing: Policy picks candidates; the database pick one winner.

Data model

You do not need a giant ERD—just enough tables to enforce invariants.

Core entities

EntityKey fieldsRole
ParkingLotlot_id, name, address, pricing configShard boundary
Floorfloor_id, lot_id, levelGroups spots
Spotspot_id, floor_id, type, status, featurescompact/regular/large; AVAILABLE/OCCUPIED/RESERVED/OUT_OF_SERVICE; EV, accessible
Vehiclevehicle_id, plate, typemotorcycle/car/truck
ParkingSessionsession_id, vehicle_id, spot_id, entry_time, exit_time, status, versionTicket: OPEN → PAID → CLOSED
Paymentpayment_id, session_id, amount, status, idempotency_key, provider refsIdempotent settlement
EntryRequestLog (optional)idempotency_key, session_id, gate_idDedup gate retries

Compatibility (physical fit)

  • Motorcycle → compact, regular, or large
  • Car → regular or large
  • Truck → large only

Layer policy on top: accessible spots require permit flag; EV spots require vehicle.isEV unless overflow policy says otherwise.

Invariants to enforce

  1. At most one active session per spot — partial unique index on (spot_id) where status = OPEN (or OPEN/PAID before CLOSED).
  2. At most one open session per vehicle (usual product rule) — unique on (vehicle_id) where active.
  3. Monotonic session lifecycle — OPEN → PAID → CLOSED; no backward transitions without audited override.
  4. Payment idempotency — same idempotency_key returns the same payment outcome; provider reference unique.

The object-oriented class diagram should mirror these rules—not replace them.

If you remember one thing: Four invariants drive every table and API: one spot, one vehicle session, monotonic lifecycle, idempotent pay.

Detailed design

This section walks the same two journeys you would draw on the whiteboard: a car entering and getting a ticket, then later paying and leaving. The goal is not a checklist of SQL verbs alone—it is to make clear who decides, what becomes durable, and what the gate displays at each step.

Entry flow (allocate spot)

When a vehicle reaches the entry lane, the driver needs one thing that feels instant: a ticket (or on-screen instructions) that names a real parking spot. Behind that moment the system must do three careful jobs. First, it must recognize the vehicle type and any preferences the product allows—floor, zone, accessible, EV. Second, it must pick a spot that is physically large enough and allowed by policy. Third, it must claim that spot for this vehicle alone so a second lane cannot be told the same spot is free.

The gate or app sends that request with an idempotency key—for example entry:{gate_id}:{request_id}. That key matters because flaky networks and double-taps are normal at barriers. If the same key arrives again, the service returns the same session and spot that already succeeded. It must not invent a second open parking session for one physical entry.

Once the request is new, the parking service builds a short list of candidate spots: matching vehicle type, status available (or reserved for this reservation id), not marked out of service, ordered by the selection policy you agreed earlier (nearest aisle, fill one floor first, and so on). Then it enters one database transaction that both locks and updates:

Inside that transaction the service asks the database for one available row using a locking read (FOR UPDATE SKIP LOCKED or an equivalent). SKIP LOCKED means “if another lane already locked this row, skip it and try another”—so two gates do not block forever waiting on the same row. When a free spot is found, the service marks it occupied, inserts a parking session in the OPEN state with a server-side entry timestamp, and only then commits. The ticket returned to the gate includes session_id, spot_id, floor, and short driving instructions.

If every candidate is gone or locked by others, the service returns a clear “lot full” (or temporarily unavailable) response—never a made-up spot id. Availability screens may still show a stale free count; entry always tries a real claim and trusts the transaction, not the sign.

There is a second style that some teams prefer: insert the OPEN session and let a unique constraint “only one OPEN session per spot” reject a double insert, then retry the next candidate. Either story is fine in the interview as long as you show why two cars cannot both win without a lock or unique rule.

Exit and payment flow

Exit is a different state machine. The driver presents the ticket (session_id) or a plate lookup if the ticket is lost. The service loads that session. If the session is already paid and closed, it returns the previous outcome—because payment confirmation messages also retry, and charging twice for one visit destroys trust.

If the session is still open, the service computes the fee from server UTC time, not from the phone’s clock. That usually means: duration since entry, minus any grace minutes, rounded by the product rule (per minute or per started hour), and with a documented policy for lost tickets or daily caps. The pricing rules should be versioned so a mid-day rate change does not silently change how old sessions were charged.

Payment is submitted to the processor with its own idempotency key, such as pay:{session_id}:{amount}:{pricing_version}. Only after the provider confirms success should the session move to PAID and then CLOSED, and only then should the spot become AVAILABLE again. Opening the gate before money clears is a product choice (pay later / valet debt)—if you allow it, track the debt explicitly. Do not casually set the spot free and hope payment finishes later unless you are ready to explain that debt model.

Receipts and analytics can leave asynchronously after the critical path is safe. The user’s mental model is simple: ticket in, pay once, gate opens once, spot frees once.

Availability reads

Screens that say “47 free spots” are a display problem, not the claim path. They typically read cached counts or a materialized view refreshed every few seconds. That lag is acceptable for humans driving past a board. It is not acceptable for the entry API, which must always attempt a transactional claim. In the room, say out loud: “Signs can be slightly wrong; entry cannot assign a ghost spot.”

In the room (spoken): “On entry I claim one compatible spot in a single database transaction, create an open session with server time, and return a ticket. Retries reuse the same idempotency key so I never open two sessions for one gate request. On exit I compute the fee from server time, charge once with a payment idempotency key, then close the session and free the spot—never the reverse unless we explicitly support pay-later.”

Architectural dig: two cars, one spot

This is the interview meat—same tension as booking the last vaccine slot, with garage gates instead of browsers.

What goes wrong (bad design: read free, then write)

t0  Lane A reads Spot 12 status = AVAILABLE
t0  Lane B reads Spot 12 status = AVAILABLE
t1  A writes OCCUPIED + inserts session SA
t1  B writes OCCUPIED + inserts session SB
    two tickets, one physical space

Or: gate times out after commit, retries /entry without idempotency—two sessions for one car. Or: exit confirm retries—double charge, or double release that frees a spot while a car still parks there.

What works (great design: row lock + one OPEN session)

BEGIN
  SELECT spot_id FROM spots
    WHERE lot_id = ? AND type compatible AND status = AVAILABLE
    ORDER BY policy
    LIMIT 1
    FOR UPDATE SKIP LOCKED
  IF no row → ROLLBACK → 409 lot full
  UPDATE spots SET status = OCCUPIED
  INSERT parking_sessions (spot_id, status=OPEN, entry_time=utc_now())
  COMMIT → return ticket

Lane B’s transaction skips the row A locked and takes the next spot—or waits briefly and fails full.

Pair with:

  • Idempotency-Key on entry — retry returns same session_id.
  • Partial unique index — one OPEN session per spot_id.
  • Idempotent payment confirm — same key returns same charge id.

Two entry lanes racing for Spot 12: naive read-then-write assigns both; FOR UPDATE SKIP LOCKED plus one OPEN session per spot yields one winner

Figure: Display cache may say “1 left”; claim on the primary decides the single winner.

Interview land: “Signs are a hint; entry is the lock—one short transaction, one winner per spot.”

Key challenges

ChallengeWhat users see if it goes wrongWhat to say in the interview
Double assignmentTwo cars, one spot; fights and liabilityRow lock or unique OPEN session; never read-then-write across requests
Gate retriesDuplicate tickets; inflated occupancyIdempotency-Key on entry mapped to session_id
Stale availability“20 free” but lot feels fullCounters from spot state machine; reconcile and alert on drift
Payment retryDouble charge at exitPayment idempotency key + provider reference uniqueness
Policy vs fitTruck in compact spot or ICE in EV baySeparate physical compatibility from policy flags
Lost ticketLong exit line, arbitrary feesPlate lookup; max-day rate; audited manual override
Sensor driftDB says empty; bay occupiedReconciliation and attendant override—not blind sensor overwrite

Correct spot assignment matters more than fancy pricing engines. Drivers forgive a slow sign; they do not forgive two tickets for one space or a double charge.

Scaling the system

Per-lot partition: Store spots and sessions under lot_id. A busy mall peaks independently; sharding prevents one garage from blocking another.

Read replicas for availability: Signs and apps poll counts from read-optimized stores or cache. Entry and exit writes stay on the primary for that lot so claims never use stale occupancy.

Hot spots within a lot: Popular floors at Saturday peak create contention on many rows—but SKIP LOCKED spreads lanes across different spot rows instead of serializing the whole lot on one counter.

Background workers: Receipt email, counter rebuild, reservation TTL expiry, sensor reconciliation—async after commit. Do not block gate open on SMTP.

Multi-region: Usually one lot lives in one region; cross-region assignment is rare. Operator dashboards aggregate asynchronously.

Watch 409 rate at entry (lot full) and claim latency p99 per lot. A spike in full responses with low occupancy in truth means cache drift or bad policy filters—not necessarily true capacity limits.

Failure handling

ScenarioWhat the user seesWhat to build
Database slow at entryGate spinner; queue backs upShort transaction; timeout with safe retry via idempotency key; do not partial-commit spot without session
Payment provider downCannot exit automaticallyQueue at exit with attendant override path; session stays OPEN; spot stays OCCUPIED
Duplicate sensor eventFlickering spot statusDedup by (sensor_id, event_id); reconcile against session truth
Cache lostSigns blank or wrongRebuild counters from spot table; entry still works from primary
Clock skew on gate deviceWrong feeServer UTC for entry_time; device clock advisory only
Attendant force releaseSpot marked free while car presentAudited override; optional sensor alert

When assignment correctness is uncertain—database unhealthy, transaction aborts mid-claim—fail closed on new entry rather than guessing free spots. A closed gate frustrates drivers; assigning two cars to one bay creates a longer support and safety incident.

API design

Keep the surface small and state-driven.

EndpointRole
POST /v1/entryAllocate spot, create OPEN session; requires Idempotency-Key
POST /v1/exitCompute fee, initiate or confirm payment
POST /v1/payments/confirmIdempotent payment confirmation for session
GET /v1/availabilityCounts by floor and spot type (may be stale)
POST /v1/spots/{spotId}/overrideAttendant OUT_OF_SERVICE / force AVAILABLE (audited)
GET /v1/sessions?plate=Lost-ticket lookup (rate-limited, audited)

GET /v1/availability parameters

ParamRole
lotIdWhich garage
floorIdOptional filter
spotTypecompact / regular / large
featuresev, accessible

Request flow

Gate → POST /v1/entry (Idempotency-Key)
  → parking.allocateSpot(txn: SKIP LOCKED)
  → createSession(OPEN)
  → 201 { session_id, spot_id, floor }

Gate → POST /v1/exit
  → computeFee(session)
  → payments.confirm(idempotent_key)
  → session PAID → CLOSED; spot AVAILABLE
  → 200 { receipt }

Errors: 409 no compatible spot (lot full); 402 payment required; 404 unknown session; 429 gate rate limit. Duplicate Idempotency-Key returns same body as first success.

If you remember one thing: Entry allocates; exit settles; payment confirms idempotently—three verbs, one lifecycle.

Observability

Do not watch only “entry API 200 rate.” That misses double assignment and cache lies.

SignalWhy it matters
entry.claim latency p99Gate SLO; long transactions block lanes
entry.conflict / 409 rateTrue full vs stale policy vs drift
spot.double_open_sessionsShould be zero—alert immediately
cache.count_drift per floorabs(cached − derived from spots)
payment.duplicate_idempotency_hitsHealthy dedup vs bug
exit.payment_failure_rateProvider or network pain at evening peak
override.count by attendantOperational load and sensor distrust

Alert ideas: any row with two OPEN sessions on one spot; drift above threshold for 10 minutes; payment confirm without matching OPEN session; spike in entry retries without idempotency headers.

If you remember one thing: Measure truth violations (double session, negative free count), not only HTTP success.

Security and abuse

Authenticate gate devices and attendant tools before entry and override APIs—anonymous callers should not mint tickets or free spots. Rate-limit entry and plate lookup to prevent scanning for open sessions. Idempotency keys must be unguessable and scoped per gate. Payment endpoints require the same auth plane as exit kiosks; store provider tokens encrypted. Attendant overrides need role-based access and immutable audit (who, when, reason). Do not log full card numbers or plates in searchable debug logs; correlate by session_id in redacted form. Stolen gate credentials need revoke and rotation without draining the whole lot inventory.

Cost awareness

Every double booking wastes attendant time, refunds, and trust. Every stale “20 free” sign wastes circling traffic and complaints—not CPU dollars, but operator cost. Caching availability is cheap; reconciling it nightly is cheaper than wrong-way drivers blocking aisles. Payment retries without idempotency create support tickets that cost more than the parking fee. Shard by lot so one viral mall does not force a city-wide database scale-up. Sensor ingestion and reconciliation have storage cost—sample and dedup events rather than writing every flicker to the primary path.

Production scenes

Textbooks teach boxes and arrows. On-call teaches this: the dashboard can look fine while drivers are furious.

Scene 1: Availability says 20 free, but the lot feels full

The moment: Saturday rush. LED signs show open spots. Drivers circle for twenty minutes. Attendants radio that level 3 is packed.

The trap: Cached counters drifted from truth. OUT_OF_SERVICE spots still counted as available. Overrides did not emit counter updates. Sensors lagged so OCCUPIED bays looked free on the board.

What to build: Derive counts only from spot status. Rebuild cache hourly and on state-change events. Alert when drift exceeds threshold. Entry still claims atomically—signs are hints, not contracts.

Scene 2: Gate retries create duplicate sessions

The moment: One vehicle has two printed tickets. Occupancy is wrong by one. Billing disputes at exit.

The trap: Network timeout after commit; gate retries POST /entry without Idempotency-Key. Backend creates a second OPEN session for the same vehicle.

What to build: Require Idempotency-Key on entry; persist key → session_id. Retries return the first ticket. Enforce one OPEN session per vehicle via unique index.

Scene 3: Double charge at payment confirmation

The moment: Card charged twice for one stay. Support queue spikes. Finance runs manual refunds.

The trap: Payment provider encourages confirm retries; each retry creates a new charge row.

What to build: PaymentIntent with idempotent confirm; store provider reference with uniqueness. Return same receipt on replay. Release spot only after terminal payment success.

Scene 4: Clock skew breaks pricing

The moment: Receipt shows negative duration. Fee jumps at daylight saving change. Driver argues at kiosk.

The trap: entry_time taken from gate device clock. Mixed time zones on receipts.

What to build: Server UTC for all session timestamps. Device time stored as metadata only. Unit tests around DST boundaries.

In the interview: Pick one scene. Tell what users felt, which metric lied, and one fix—state machine plus idempotency plus constraints—not “we would monitor it.”

Bottlenecks and tradeoffs

Exact occupancy vs fast displays

The tension: Drivers want instant sign updates; exact per-spot truth on every LED is expensive and still stale for seconds.

What breaks: Treating display cache as source of truth for assignment.

What teams do: Transactional claim on primary; approximate counters for boards; reconcile continuously.

Say in the interview: “Signs lag; entry never lies.”

Row lock contention vs lot full latency

The tension: FOR UPDATE SKIP LOCKED spreads lanes across rows; when the lot is truly full, every lane scans candidates and fails.

What breaks: Long entry latency at 99% occupancy if candidate queries are unindexed.

What teams do: Index (lot_id, type, status); short-circuit when derived count is zero; keep transactions tiny.

Say in the interview: “Index the candidate query; fail fast when policy says full.”

Pay on exit vs pay on entry

The tension: Exit payment matches actual stay duration; entry prepay speeds exit but refunds get messy.

What breaks: Releasing spot before pay on exit if confirm is slow—another car assigned while first still parked.

What teams do: Default pay-on-exit with idempotent confirm; optional prepay with explicit “spot held until paid” policy.

Say in the interview: “I release the spot only after payment succeeds unless attendant pay-later is explicit.”

If you remember one thing: The bottleneck is usually correctness under retry, not QPS.

Interview tips

You have the full design in mind now. These five back-and-forths are how interviewers test whether you can defend it under pressure.

“Nearest available spot”

You might say: “We pick the nearest free spot.”

Interview Push: “Nearest to what—the main entrance, the elevator, or the driver’s declared zone?”

Land here: Define nearest as a written policy: same zone first, then lowest floor, then lowest spot number—or walking distance from a fixed entrance. Allocation still uses atomic claim on one spot row; “nearest” only orders the candidate set.

Source of truth for availability

You might say: “We keep availability in Redis for speed.”

Interview Push: “Two entry lanes both see ‘Spot 12 free’—who wins?”

Land here: Spot occupancy lives in the database (transaction or unique constraint). Cached counts power displays—they can lag briefly but must reconcile from spot status. Signs do not assign; entry does.

Concurrency and locks

You might say: “We use a lock.”

Interview Push: “What exactly is locked—a spot, a floor pool, or the whole lot?”

Land here: Lock the chosen spot row with SELECT … FOR UPDATE SKIP LOCKED, or enforce one OPEN session per spot via partial unique index. Pair with Idempotency-Key on entry so gate retries do not create two tickets.

Pricing and time

You might say: “Fee equals hours times rate.”

Interview Push: “Grace period? Rounding per minute or per hour? Lost ticket?”

Land here: State rounding rules, grace minutes, and max-day rate for lost tickets. Use server UTC for session timestamps—not the gate device clock.

Overrides and messy reality

You might say: “Sensors always tell the truth.”

Interview Push: “A spot shows available but a car is physically there—now what?”

Land here: Attendants get an audited override API (OUT_OF_SERVICE, force release). Treat sensor events as at-least-once; reconcile divergent state instead of pretending hardware is perfect.

After each push, name one mechanism—row lock, idempotency key, unique constraint, or reconciliation job—not “we’ll handle it.”

What should stick

After reading this guide, you should be able to explain:

  1. Invariants first — One active session per spot; idempotent pay; monotonic lifecycle—before any class diagram.
  2. Truth vs display — Database owns occupancy; cache owns fast counts with reconciliation.
  3. Atomic claimFOR UPDATE SKIP LOCKED or unique OPEN session stops two cars in one spot.
  4. Idempotent gates — Retries on entry and payment must not duplicate tickets or charges.
  5. Messy world is normal — Sensors lie, attendants override, tickets get lost—design reconciliation paths.

Tell it in the room: “I define vehicle and spot rules and invariants first. Entry atomically claims one compatible spot and creates an OPEN session with an Idempotency-Key. Exit computes fee from server time, pays once idempotently, then releases the spot once. Availability displays use cached counts rebuilt from the spot state machine—not the other way around.”

Key Takeaways

  • Invariants before classes — One spot, one open session, monotonic ticket, idempotent payment.
  • Entry is the lock — One transaction claims a row; signs and cache are hints.
  • SKIP LOCKED — Multiple lanes skip each other’s rows instead of blocking the whole lot.
  • Idempotency-Key — Gate and payment retries return the same outcome.
  • Exit order — Fee → pay → PAID → CLOSED → release spot once.
  • Reconciliation — Counters and sensors follow session truth, not replace it.
  • Shard by lot — Multi-lot scale partitions inventory; browse can federate.

Quick revision notes

Use this as a last look before a mock interview, not as a substitute for the full guide.

Clarify first. Agree vehicle and spot types, allocation policy, payment timing, reservations vs walk-in, attendant overrides, and what you leave out of scope.

State invariants. One active session per spot; monotonic OPEN → PAID → CLOSED; idempotent payment; server UTC times.

Draw two paths. Entry: candidate query → FOR UPDATE SKIP LOCKED → OCCUPIED + OPEN session. Exit: fee → idempotent pay → release spot once.

Truth vs display. Cache counts for signs; claim always on primary; reconcile drift.

Name the dig. Reject read-free-then-write; one transaction one winner; image the two-cars-one-spot race.

Operate reality. Watch double-session violations, cache drift, payment dedup hits—not only entry 200 rate.

Practice next: Open Parking Lot on Practice System Design and run a timed attempt using the clarifying questions and entry/exit walkthrough above.

What interviewers expect

  • Clarify rules first: vehicle types, spot types, allocation policy (nearest, lowest floor, zone), payment timing, reservations vs walk-in.
  • Invariants before classes: at most one active session per spot; ticket lifecycle OPEN → PAID → CLOSED; payment idempotent on retries.
  • Atomic claim on entry: SELECT … FOR UPDATE SKIP LOCKED on a candidate spot row, or a unique constraint on one OPEN session per spot.
  • Truth vs display: database owns occupancy; cached counters power signs and apps with periodic reconciliation.
  • Entry and exit idempotency: gate retries must not create duplicate tickets; payment confirmation must not double-charge.
  • Messy ops: lost tickets, attendant overrides, sensor drift—reconciliation paths, not “sensors always win.”

Interview workflow (template)

  1. Clarify requirements. Confirm functional scope, users, consistency needs, and which non-functional goals matter most (latency, availability, cost).
  2. Rough capacity. Estimate QPS, storage, and bandwidth so your data model and partitioning story are grounded.
  3. APIs and core flows. Define a minimal API and walk 1–2 critical read/write paths end to end.
  4. Data model and storage. Choose stores for each access pattern; call out hot keys, indexes, and retention.
  5. Scale and failure. Add caching, sharding, replication, queues, or fan-out as needed; say what breaks in failure modes.
  6. Tradeoffs. Name alternatives you rejected and why (e.g. strong vs eventual consistency, sync vs async).

Frequently asked follow-ups

  • How do you assign a spot without double booking?
  • How do you handle concurrency at multiple entry gates?
  • Walk through entry and exit with payment.
  • How do you show availability when the lot is almost full?
  • How does this scale to many independent parking lots?

Deep-dive questions and strong answer outlines

How do you assign a spot without two cars taking the same space?

I would not read “spot is free” and update later in a second request. Inside one short database transaction, pick a compatible candidate spot, lock that row with SELECT … FOR UPDATE SKIP LOCKED, flip status to OCCUPIED, and insert a ParkingSession with status OPEN. The second lane skips the locked row and tries the next candidate—or returns “lot full” if none remain. Optionally enforce a partial unique index: only one OPEN session per spot_id. Pair with an Idempotency-Key on entry so gate retries do not create two tickets for one vehicle.

How do multiple entry lanes coordinate on the same lot?

Each lane calls the same entry API backed by one authoritative database for that lot. Allocation is row-level, not “each gate keeps its own free list.” Fast “spots remaining” signs read from cached counters rebuilt from spot state, but the claim always hits the primary. If two lanes race, one transaction wins; the other gets the next spot or a clear “full” response. For very large garages, shard by lot_id so one mall does not serialize the whole city.

Walk through entry and exit with payment.

Entry: vehicle type and optional preferences → find compatible AVAILABLE spots → atomic claim → create OPEN session with server UTC entry_time → return ticket (session_id, spot_id, directions). Require Idempotency-Key so a timeout retry returns the same session. Exit: lookup by session_id or plate (lost ticket) → compute fee from entry_time with stated grace and rounding rules → create/confirm Payment with idempotency key → on success transition session OPEN → PAID → CLOSED → release spot to AVAILABLE exactly once. Do not release the spot before payment succeeds unless you explicitly allow pay-later with attendant override.

How do you handle a full lot signal at the gate?

The gate needs a fast answer, but “full” must be honest enough. Maintain derived counters per floor and spot type from the spot state machine (AVAILABLE, OCCUPIED, RESERVED, OUT_OF_SERVICE). Entry still does a real claim in a transaction—cached zero means “probably full,” not a guarantee. Reconcile counters hourly or on state change events; alert when abs(cached − derived) exceeds a threshold. Stop admitting when no compatible spot can be claimed, not only when a stale counter says zero.

How does this scale to many parking lots?

Shard by lot_id: each garage (or operator region) has its own service boundary and database partition. Cross-lot search is a read aggregator; entry and exit writes stay on the lot that owns the spots. Hot rows appear per popular lot at peak, not one national inventory row. Multi-lot operators share payment and reporting asynchronously; they do not merge spot assignment into one global counter.

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 only an object-oriented design question?

A: Many interviews start with entities and state machines—that is fair. At InterviewCrafted depth, treat it as system design with concurrency: invariants, transactional assignment, idempotent gates, and how displays relate to truth. Say your classes aloud, then show the database mechanism that stops two cars in one spot.

Q: How do reservations work alongside walk-in entry?

A: Reservations hold a spot (or a spot type pool) with a TTL before arrival. Walk-in entry claims from AVAILABLE spots not held or expired. Use the same atomic claim path for both: RESERVED → OCCUPIED on check-in, or AVAILABLE → OCCUPIED for walk-in. A nightly job frees expired holds back to AVAILABLE. In the interview, separate hold inventory from walk-in inventory early so flash walk-in traffic does not steal reserved EV spots without policy.

Q: What if IoT sensors disagree with the database?

A: Treat sensor events as at-least-once hints, not the sole source of truth. The database reflects legal occupancy (ticket OPEN). If a sensor says occupied but DB says AVAILABLE, flag for attendant review—do not silently overwrite paid sessions. Reconciliation jobs compare sensor streams to session rows and open override tickets. Good teams measure divergence rate per floor.

Q: How does valet parking fit?

A: Valet is a parallel queue state machine: vehicle checked in at curb, spot assigned by attendant app using the same atomic claim API, keys tracked separately. Payment may happen at return, not at garage exit gate. Scope it as a different entry channel sharing spot inventory—not a second parking lot with duplicate spot rows.

Q: Should availability boards show exact spot numbers or only counts?

A: Counts per floor and type are enough for most garages and are cheaper to cache. Showing “Spot 214” on a sign is optional and increases stale display risk. If you show specific spots, treat it as a hint; entry still claims atomically. Say aloud which UX you picked and how often boards reconcile.

Q: What about dynamic pricing by hour or demand?

A: Store a pricing rules version on the session at entry (or re-rate at exit with a declared policy). Fee calculation uses server UTC timestamps, grace minutes, rounding rules, and a max-day cap for lost tickets. Payment idempotency keys include amount and pricing_version so a retry after a rule change does not double-charge or charge the wrong tier.

Practice interactively

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