Design a Parking Lot — Low-Level Design
Fast recall
Quick revision
Core answer
ParkingLot containing multiple Floors, each containing typed Spots (COMPACT, LARGE, EV, HANDICAPPED). On entry, a SpotAllocator picks a free spot for the vehicle's category and atomically marks it occupied; a Ticket is created with entry time. On exit, a FeeStrategy computes the bill using ticket entry/exit times and spot type. Use interfaces for the variation points — SpotAllocator (nearest, by-floor, EV-priority), FeeStrategy (hourly, slabbed, day-pass). Make the spot reservation atomic via ConcurrentHashMap.compute or per-floor locks to handle concurrent arrivals. Persist tickets via a TicketRepository if state must survive restarts. Keep the design swappable, thread-safe, and testable.Decomposition into entities + interfaces, atomic spot allocation under concurrency, separation of fee policy via Strategy, and extensibility for spot types / pricing models / reservations.
Single god `ParkingLotService`; non-atomic spot reservation (two cars get same spot); deep inheritance for vehicle types; fee logic baked into the lot.
Discuss per-floor partitioning for scalability, EV / handicapped spot priority, reservation extension, and persistence vs in-memory trade-off.
Interview-ready guide
Interview answer
Problem framing
Vehicles arrive at gates, get assigned a spot, receive a ticket. On exit, spot freed and bill computed. Multiple floors; spots come in different sizes; pricing varies. Design pressure is concurrency (multiple gates) and extensibility (new spot types, fee structures, reservations) — not raw scale.
Clarifying questions
Lot shape (single, multiple floors, count). Vehicle types. Spot types matched 1:1 or generic. Reservations or walk-in. Pricing (flat hourly, slabbed, day pass). Multiple gates. Persistence. Payment in scope.
Assumptions
Single lot, multiple floors. Spot types COMPACT/LARGE/EV/HANDICAPPED. Vehicle categories BIKE/CAR/TRUCK/EV — each maps to compatible spot types. Walk-in (reservations as extension). Hourly pricing per spot type (slabbed as extension). Multiple concurrent gates; atomic reservation. In-memory state with TicketRepository interface for future persistence. Payment out of scope; unpark returns Receipt with amount due.
Core entities
ParkingLot: entry point, holds floors, allocator, fee strategy, ticket repo. park(vehicle), unpark(ticket). Floor: owns list of spots, finds free spot for a type. Spot: one parking position. Type, occupied flag, current vehicle. Vehicle: license plate, category. Category determines compatible spot types. Ticket: immutable record (id, vehicle, spot, entry time). Receipt: immutable record (ticket, exit time, amount). SpotAllocator interface: NearestSpotAllocator, EvPriorityAllocator. FeeStrategy interface: HourlyFeeStrategy, SlabbedFeeStrategy. TicketRepository interface: in-memory impl for now.
Atomic spot reservation
Two cars at two gates must not get same spot.
Per-spot atomic check-and-set (recommended): Spot has AtomicReference
Alternatives: per-floor synchronized lock (simpler, serializes assignments per floor). Distributed Redis SET NX (for cross-instance, mention only if asked about distribution).
Code — lot, allocator, fee strategy
ParkingLot.park(v): allocator.allocate(floors, v) or throw LotFullException. Create Ticket with TicketId.next(), v, spot, clock.instant(). tickets.save(t). Return t.
ParkingLot.unpark(id): tickets.findById, throw InvalidTicketException if missing. exit = clock.instant(). amount = feeStrategy.calculate(t, exit). t.spot().release(). tickets.remove(id). Return new Receipt.
NearestSpotAllocator: iterate floors (ordered by proximity), iterate compatible SpotTypes for vehicle category, findFree on each floor, tryAssign. First successful CAS wins.
HourlyFeeStrategy: minutes between entry and exit, round up to hours, minimum charge of 1 hour, multiply hourlyRates[spot.type] by hours.
Clock injection — testable without freezing real time.
End-to-end flow
Vehicle arrives → EntryGate calls park(vehicle) → Lot calls allocator.allocate → spot.tryAssign (CAS) → lot creates ticket → saves → returns to gate → vehicle gets ticket. Time passes. Vehicle presents ticket → unpark(ticketId) → findById → feeStrategy.calculate → spot.release → remove ticket → Receipt.
Extensibility
- New spot type → add to enum + compatibleWith mapping; allocator/fee untouched
- New pricing → new FeeStrategy impl; configure at construction
- New allocation policy → new SpotAllocator (EvPriorityAllocator); lot wiring picks one
- Reservations → ReservationService holds spots with TTL; park() checks active reservation matching vehicle first
- Multi-lot → LotDirectory picks a lot; lot-level logic unchanged
- Persistence → JpaTicketRepository implements interface; spots persisted with optimistic lock
- Notifications → publish SpotAssignedEvent / SpotReleasedEvent; subscribers update displays/metrics
Failure modes
- Lost ticket: LostTicketPolicy — max-day rate or plate verification
- Lot full: LotFullException; gate displays 'Full'
- Concurrent unpark on same ticket: tickets.remove returns whether succeeded; idempotent
- Crash mid-park: ticket saved but receipt fails. Idempotency key on park (plate + entry minute); duplicate calls return same ticket
- Spot stuck occupied: nightly reconciliation job; flags spots beyond max stay duration; manual override
- Time skew: all entry/exit from single Clock; centralized
Trade-offs and alternatives
- In-memory vs persistent: in-memory simpler/faster but loses state on restart; persistent survives crash with latency cost. Driven by tolerance for losing seconds of state
- Per-spot CAS vs per-floor lock: CAS scales better; per-floor lock simpler but serializes per floor
- Strategy vs rule engine for fees: Strategy enough for 5-10 variants; rule engine (Drools) overkill until customer-configurable tariffs
- Inheritance for vehicle types: avoid; enum + compatibility table
- Distributed coordination: Redis SET NX for multi-instance; only for multi-region operator
Recommendation
Build a single ParkingLot aggregating typed Floors and Spots, with SpotAllocator and FeeStrategy as interfaces so allocation policy and pricing are swappable. Make spot assignment lock-free using AtomicReference + compareAndSet on each Spot, so concurrent gates can't double-book. Inject Clock and TicketRepository for testability and future persistence. Use enum + compatibility mapping for vehicle/spot types instead of inheritance hierarchies. Reservations, persistence, and multi-lot are clear extensions that drop in without touching the core flow.
Design a Parking Lot — Low-Level Design
Direct Answer
Model a parking lot as a ParkingLot containing multiple Floors, each containing typed Spots (COMPACT, LARGE, EV, HANDICAPPED). On entry, a SpotAllocator picks a free spot for the vehicle's category and atomically marks it occupied; a Ticket is created with entry time. On exit, a FeeStrategy computes the bill using ticket entry/exit times and spot type. Use interfaces for the variation points — SpotAllocator (nearest, by-floor, EV-priority), FeeStrategy (hourly, slabbed, day-pass). Make the spot reservation atomic via ConcurrentHashMap.compute or per-floor locks to handle concurrent arrivals. Persist tickets via a TicketRepository if state must survive restarts. Keep the design swappable, thread-safe, and testable.
Common Mistakes & Pitfalls
Single god ParkingLotService; non-atomic spot reservation (two cars get same spot); deep inheritance for vehicle types; fee logic baked into the lot.
Check your understanding
Answer these without looking back at the article.
- How would you extend this design to support reservations with TTL?
- What changes if you have to support thousands of lots across regions from a single API?
- How do you handle a lost ticket cleanly without bolting it onto the fee strategy?
- Walk through how `Spot.tryAssign` prevents two cars from being assigned the same spot.
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.