Skip to main content

Design a Parking Lot System

A deliberately different kind of "easy" problem from the rest of this section: this isn't really an internet-scale distributed systems question — a single parking garage has thousands of spots, not millions of users. The interesting problem here is correctness under concurrency at a small scale: multiple entry gates trying to assign a spot to different cars at the same instant must never assign the same spot twice. Following the framework from How to Answer a System Design Interview Question.

1. Requirements

Functional:

  • A vehicle enters, is assigned an available spot appropriate for its size (motorcycle, compact, large), and receives a ticket.
  • A vehicle exits, its ticket is scanned, a fee is calculated based on duration, and its spot becomes available again.
  • Support multiple entry/exit gates and multiple levels, each with a countable number of spots per vehicle size.

Non-functional:

  • Correctness matters far more than raw throughput here — this system handles, at most, a few vehicles per second even at a very large garage, nowhere near the scale that motivates horizontal partitioning.
  • The one property that must never be violated: two vehicles must never be assigned the same spot.

Scale estimate: a large garage might have 10,000 spots and see a few hundred entries per hour at rush periods — a workload any single well-designed database can handle comfortably. The design challenge here is concurrency correctness, not throughput.

2. API Design

POST /entry body: { gate_id, vehicle_type } returns: { ticket_id, spot_id }
POST /exit body: { ticket_id } returns: { fee, duration }
GET /availability returns: { compact: int, large: int, motorcycle: int }

3. Data Model

spots
spot_id VARCHAR PRIMARY KEY
level INT
size ENUM(motorcycle, compact, large)
status ENUM(available, occupied)

tickets
ticket_id VARCHAR PRIMARY KEY
spot_id VARCHAR FOREIGN KEY -> spots
entry_time TIMESTAMP
exit_time TIMESTAMP NULL

A spot's status field is the crux of the entire design — it's the one piece of shared state that every concurrent entry request is racing to read and update correctly.

4. High-Level Design

System Design Lab

This is intentionally a small, mostly single-database design — the scale estimate doesn't justify sharding or a distributed cache, and proposing them anyway would be solving a scale problem this system doesn't have, at the direct expense of time spent on the concurrency problem it does have.

5. Deep Dive: assigning a spot without double-booking

Two vehicles arriving at two different gates within the same instant, both looking for a compact spot, must not both be assigned the same available spot. Naively reading "find an available compact spot" and then writing "mark it occupied" as two separate steps is exactly the race condition worth catching here: both gates' requests can read the same spot as available before either one writes back that it's now occupied.

The fix is the same one covered in ACID Transactions: wrap the read-and-update in a single transaction with appropriate row-level locking (SELECT ... FOR UPDATE in a relational database) so that once one transaction has read a candidate spot, a second concurrent transaction can't read that same row as available until the first has committed its update — serializing exactly the one operation that needs it, without needing to lock the entire table. This is a small-scale, single-database instance of the same correctness problem Distributed Locking solves across multiple machines — here, one database's own transaction isolation is sufficient, precisely because everything fits on one machine and doesn't need cross-node coordination at all.

6. Tradeoffs

Row-level locking serializes spot assignment only for the specific row being contended, so two gates assigning different spots don't block each other at all — only genuine contention for the same spot serializes, which is exactly the granularity this problem needs: correct where it matters, without sacrificing throughput where it doesn't.

One small operational edge case worth naming: a vehicle that never scans out (a broken exit gate, a car towed off-site) leaves its spot marked occupied forever, even though it's physically empty — the same "how do you know a resource was actually released" problem Distributed Locking solves with a lease timeout, applied here at a much smaller scale: flagging any ticket open far longer than any plausible parking duration for manual review, rather than trusting the exit scan as the only way a spot ever becomes available again.

Row-level locking vs. a single global lock for spot assignment: pros and cons

Row-level locking

  • Only genuinely contended spots ever serialize — unrelated assignments proceed freely
  • Scales naturally as the number of spots and gates grows
  • Matches the actual granularity of the correctness requirement exactly

A single global lock

  • Serializes every single spot assignment, even ones for entirely different spots
  • Creates a throughput bottleneck that grows worse as more gates are added
  • Solves correctness at the cost of unnecessary contention the problem doesn't require

Further Reading

Share this lesson

Saved locally in your browser — visible in the sidebar as you go.