Design a Parking Lot
The canonical LLD warm-up — multi-level lots, vehicle/spot types, ticketing, and pluggable pricing.
Problem Statement
Design the object model for a multi-level parking lot that a facility operator can run day to day. Vehicles of different sizes enter, are assigned a suitable free spot, receive a ticket, and pay a fee computed from how long they stayed when they exit.
The interview is about the class model — responsibilities, relationships, and the seams that keep the design extensible for new vehicle types, spot types, and pricing rules — not about a database schema or a REST API.
Business context
Parking Lot is the canonical warm-up LLD question at Amazon, Google, and most product companies. It is deceptively rich: it exercises enums, an inheritance hierarchy, composition (Lot → Level → Spot), the Strategy pattern for pricing, a Factory for object creation, and a real concurrency concern (two cars racing for the last spot). Interviewers use it to see whether you can keep responsibilities in the right place and defend your boundaries.
Functional Requirements
Support multiple levels, each with a fixed set of spots.
Spots come in sizes (motorcycle, compact, large); a vehicle may occupy any spot that can hold its size.
Park a vehicle: find and reserve a suitable free spot, then issue a ticket.
Unpark a vehicle by ticket: free the spot and compute the fee for the stay.
Report real-time availability of free spots by size, per level.
Reject entry gracefully when the lot is full for the requested vehicle size.
Non-Functional Requirements
Correctness under concurrency
Two entry gates must never assign the same spot. Park/unpark on the lot are the critical section.
Extensibility
New vehicle types, spot sizes, and pricing plans should slot in without editing the parking flow.
Low latency
A park request is a small in-memory scan — effectively O(spots on a level) and interactive.
Encapsulation
Fit rules and occupancy state live on the domain objects, not in the controller orchestrating them.
Requirement Clarification
QCan a large vehicle occupy multiple small spots?
Out of scope for the base design — one vehicle takes exactly one spot that can hold its size. Multi-spot allocation is a documented extension.
QIs pricing hourly, daily, or tiered?
Assume a pluggable pricing plan. The base implementation ships an hourly strategy, but the exit path must not hard-code it.
QDo we need to model payments, gates, or an entry display board?
Not for the core model. We expose park, unpark, and availability; payment/hardware are adapters layered on top.
QAre spot assignments persisted across restarts?
In-memory is fine for the interview. We keep state on the objects and note where a repository would plug in for durability.
UML Class Diagram
Sequence Diagram
Entity Identification
ParkingLot
Aggregate root. Owns levels + the fee strategy, keeps the map of active tickets, and is the single synchronized entry point for park/unpark.
Level
Owns the spots on one floor and knows how to find + reserve the first spot that fits a vehicle.
ParkingSpot
Holds occupancy state and the fit rule (canFit). Guards its own invariant — you cannot park in an occupied or too-small spot.
Vehicle
Abstract base carrying plate + size; Motorcycle/Car/Bus fix the size. Knows whether it fits a given spot size.
Ticket
Immutable record of a parking session: the reserved spot, the vehicle, and the entry timestamp used for billing.
ParkingFeeStrategy
Pricing policy interface. HourlyFeeStrategy is the default; new plans (daily, weekend, EV) implement the same method.
VehicleSize
Ordered enum (MOTORCYCLE < COMPACT < LARGE) whose ordinal encodes the fit rule via canHold.
Design Patterns Used
ParkingFeeStrategy isolates pricing. The exit path calls calculateFee without knowing whether it is hourly, daily, or promotional — a new plan is a new class, not an edit.
ParkingFactory centralises vehicle + spot creation so tests, demos, and gates build objects consistently and the concrete Motorcycle/Car/Bus types stay package-private.
The abstract Vehicle fixes the plate/size skeleton and the fitsIn rule; subclasses only supply their size, avoiding duplicated fit logic.
Step-by-Step Design
1Model sizes and put the fit rule in one place
Use an ordered VehicleSize enum and express compatibility as canHold on it. Every other class asks the enum instead of re-deriving the rule.
public enum VehicleSize { MOTORCYCLE, COMPACT, LARGE; public boolean canHold(VehicleSize needed) { return ordinal() >= needed.ordinal(); } }2Let the spot guard its own state
ParkingSpot.canFit checks both free and big enough; park/unpark throw on invariant violations so no caller can corrupt occupancy.
public boolean canFit(Vehicle candidate) { return isFree() && candidate.fitsIn(size); }3Let each level own spot selection
Level.reserveSpotFor scans its spots, reserves the first fit, and returns an Optional. The lot never iterates raw spots.
4Issue tickets at the lot boundary, atomically
ParkingLot.park tries levels in order and only creates a Ticket after a spot is reserved. park/unpark are synchronized so the check-then-assign cannot race.
5Price exits behind a strategy
unpark validates the ticket, removes it, asks the injected ParkingFeeStrategy for the fee, then frees the spot. Pricing is swappable at construction time.
Complete Java Implementation
Explanation of Every Class
VehicleSize
An ordered enum. Because LARGE > COMPACT > MOTORCYCLE by ordinal, canHold is a one-liner and the fit rule lives in exactly one place.
Vehicle / Motorcycle / Car / Bus
Vehicle validates the plate and stores the size; fitsIn delegates to VehicleSize.canHold. The three subclasses only pin their size, so there is no duplicated logic.
ParkingSpot
Holds id, size, and the current vehicle. canFit combines free + size checks; park/unpark enforce the invariant and throw rather than silently misbehave.
Level
Owns a floor's spots. reserveSpotFor does the linear scan-and-reserve; freeSpotsBySize powers the availability board using an EnumMap.
Ticket
Immutable session record created with a random id and the entry Instant. It ties a vehicle to its reserved spot for billing on exit.
ParkingFeeStrategy / HourlyFeeStrategy
The interface is the pricing seam. HourlyFeeStrategy rounds partial hours up and multiplies by a cents-per-hour rate; other plans implement the same method.
ParkingFactory
Static factory that maps a type string to the right Vehicle subclass and builds spot ids consistently. Keeps the concrete vehicle classes package-private.
ParkingLot
The aggregate root. park/unpark/availability are synchronized; it owns the active-ticket map and delegates spot selection to levels and pricing to the strategy.
Dry Run
Sample input
Lot with 1 level: spots [MOTORCYCLE, COMPACT, LARGE]. Actions: park a Car, park a Bus, unpark the Car after 90 minutes at 200¢/hour.
| Step | Action | Spot chosen | Active tickets | Result |
|---|---|---|---|---|
| 1 | park(Car · COMPACT) | COMPACT spot | {T1} | Ticket T1 |
| 2 | park(Bus · LARGE) | LARGE spot | {T1, T2} | Ticket T2 |
| 3 | unpark(T1) @ +90 min | COMPACT freed | {T2} | ceil(90/60)=2h × 200¢ = 400¢ |
| 4 | availability() | — | {T2} | COMPACT:1, MOTORCYCLE:1, LARGE:0 |
Step 3 shows the hourly strategy rounding 90 minutes up to 2 hours, and freeing the spot the moment the ticket is settled.
Complexity Analysis
| Operation | Time | Space | Note |
|---|---|---|---|
| park | O(L × S) | O(1) | L levels, S spots per level — a linear scan for the first fit. |
| unpark | O(1) | O(1) | Map removal + a constant-time fee computation. |
| availability | O(L × S) | O(L) | Counts free spots by size across every level. |
Park is the hot path. If S grows large, replace the per-level linear scan with size-bucketed free-spot queues to make park O(1) as well (see Alternative Designs).
Extensibility
New vehicle / spot size
Add an enum constant (respecting order) and, if needed, a Vehicle subclass. The fit rule and every scan keep working.
New pricing plan
Implement ParkingFeeStrategy (daily cap, weekend rate, EV surcharge) and inject it — no change to park/unpark.
Reservations / EV charging
Add a SpotFeature set or a decorator on ParkingSpot; canFit grows an extra predicate without touching the lot.
Multiple entrances / durability
Swap the in-memory ticket map for a TicketRepository; the synchronized methods become the transaction boundary.
Alternative Designs
Size-bucketed free-spot queues
Keep a per-size map (VehicleSize to a deque of free spots) per level so park pops in O(1) instead of scanning.
Tradeoffs
Faster park at the cost of extra bookkeeping on every park/unpark and more state to keep consistent under concurrency.
Central **SpotManager** service
Pull spot selection out of Level into a dedicated allocator that sees all levels at once (e.g. to balance load).
Tradeoffs
Enables global policies but weakens the clean Lot→Level→Spot ownership and centralises a lock.
Lock-per-level instead of lock-on-lot
Synchronize each Level so cars entering different floors don't contend on one lock.
Tradeoffs
Higher throughput, but the availability snapshot is no longer a single atomic view.
Common Mistakes
- ×
Putting the fit rule in the controller (if size == ...) instead of on VehicleSize/ParkingSpot.
- ×
Creating the ticket before a spot is actually reserved, so a failed park still bills the driver.
- ×
Forgetting to synchronize park/unpark, letting two gates hand out the same spot.
- ×
Hard-coding hourly pricing in the exit path instead of behind a strategy.
- ×
Exposing mutable internal lists (spots, tickets) so callers can corrupt occupancy state.
- ×
Modelling vehicle types with an enum + switch instead of polymorphism, which then leaks into every method.
Follow-up Interview Questions
QHow do you make park O(1) under heavy load?
Maintain per-size free-spot queues per level and pop the head; push back on unpark. Trades a little memory + bookkeeping for constant-time allocation.
QHow would you support parking a large vehicle across two spots?
Introduce a SpotGroup/allocation abstraction so a reservation can hold N adjacent spots atomically; canFit becomes a group-level check.
QTwo entrances race for the last spot — what happens?
Because park is synchronized (or the level lock is held during check-then-reserve), the reservation is atomic; the loser gets Optional.empty() and is told the lot is full.
QWhere would payments and gate hardware live?
As adapters at the boundary — a PaymentProcessor and gate controllers call into park/unpark. The domain stays free of I/O.
Production Considerations
Persistence
Back tickets + occupancy with a TicketRepository (SQL) so state survives restarts and supports multiple app instances.
Concurrency at scale
A single lock becomes a bottleneck. Move to per-level locks or optimistic reservation with a DB row lock on the spot.
Observability
Emit metrics for occupancy %, park failures, and average dwell time; alert when a size class is chronically full.
Pricing correctness
Compute fees server-side with a fixed clock source, round consistently, and keep an audit trail of the rate applied to each ticket.
What Interviewers Look For
Did you keep the fit rule in one place and out of the orchestration code?
Is object creation atomic — no ticket without a reserved spot?
Did you name the concurrency problem and guard the critical section?
Is pricing behind an interface so a new plan is additive?
Can you extend to new vehicle/spot types by adding data, not editing flow?
Quiz
0/4 answered
1.Why is **canHold** defined on **VehicleSize** rather than in **ParkingLot**?
2.What does making **ParkingFeeStrategy** an interface buy you?
3.Why are **park** and **unpark** synchronized?
4.Why create the **Ticket** only after a spot is reserved?
Practice Variants
Add EV charging spots
IntermediateIntroduce a spot feature so only EVs may take charging spots, and prefer non-charging spots for non-EVs. Keep the fit rule extensible.
Daily-cap pricing
BeginnerAdd a strategy that charges hourly but never more than a daily maximum. Verify the exit path needs no changes.
Nearest-spot allocation
AdvancedAllocate the free spot closest to an entrance. Decide whether this belongs in Level, a new allocator, or a comparator.
Flashcards
Cheat Sheet
Entities: ParkingLot → Level → ParkingSpot; Vehicle (abstract) + Motorcycle/Car/Bus; Ticket; ParkingFeeStrategy; VehicleSize.
Patterns: Strategy (pricing), Factory (creation), Template Method (Vehicle base).
Flows: park = try levels → reserve first fit → issue ticket; unpark = validate ticket → compute fee → free spot.
Invariants: one vehicle per spot; ticket only after reservation; park/unpark atomic.
Complexity: park O(L×S), unpark O(1), availability O(L×S).
Extend: new size = enum constant; new price = new strategy; reservations = spot feature/decorator.
References
- BookHead First Design Patterns (Strategy, Factory) — Freeman & Robson
- BookEffective Java — Item 34 (enums), Item 1 (static factories) — Joshua Bloch
- DocsRefactoring Guru — Strategy Pattern