Step 1 Understand it
Why interviewers ask this. It's the classic first LLD question: the entities are clear, there are natural places for patterns (pricing, spot assignment), and there's a real concurrency problem.
Watch it happen
Read the animation as text
- Version 1 of the class design: ParkingLot, Spot, Vehicle, Ticket. Spots come in sizes S, M and L; spots 1 and 3 are already taken.
- A car arrives at the gate. The spot-assignment STRATEGY looks for the smallest free spot a car fits: M or L, so not spot 2.
- Spot 4 fits. It is marked taken in ONE atomic step — so a car at another gate can't be given the same spot at the same moment.
- A ticket is issued for spot 4. The display board subscribes to spot changes (Observer), so its count updates by itself.
- Exit: scan the ticket. The PRICING strategy works out the fee from the time parked; after payment, the spot is freed and the board updates again.
Requirements
Functional
- Several floors, with spots of different sizes (small, compact, large).
- Vehicles (bike, car, truck) park in a suitable free spot.
- Issue a ticket on entry; calculate the fee on exit; take payment.
- Show free spots per floor on display boards.
Non-functional
- Two vehicles must never get the same spot, even at different gates.
- Easy to add new vehicle types or pricing rules.
Classes
- ParkingLot: floors and gates; finds and assigns spots.
- Floor: its spots, and free counts by size.
- Spot: id, size, current vehicle (or none).
- Vehicle (abstract) → Bike, Car, Truck: each says which spot size it needs.
- Ticket: id, vehicle, spot, entry time.
- SpotAssignmentStrategy: for example, nearest free spot first.
- PricingStrategy: hourly, weekend, and so on.
- Payment: amount, method, status.
Patterns used
- (spot assignment, pricing)
- (creating vehicles and spots)
- (display boards)
- or one ParkingLot
Start simple, then scale
- Version 1: one floor, one gate, one spot size, a flat fee. : ParkingLot, Spot, Vehicle, Ticket.
- Version 2: several spot sizes and vehicle types, a rule for which vehicle fits which spot, and pricing as a strategy.
- Version 3: several floors and gates, with display boards observing the free counts.
- Version 4: concurrency — claiming a spot — and storing state in a so gates on different agree.
Picture it
classDiagram
class PricingStrategy {
<<interface>>
+fee(hours)
}
class SpotAssignmentStrategy {
<<interface>>
+find_spot(vehicle)
}
ParkingLot "1" *-- "many" Floor
Floor "1" *-- "many" Spot
Spot --> "0..1" Vehicle
Vehicle <|-- Car
Vehicle <|-- Truck
Vehicle <|-- Bike
Ticket --> Spot
Ticket --> Vehicle
ParkingLot --> PricingStrategy
ParkingLot --> SpotAssignmentStrategyThe design, step by step
- Entry: a vehicle arrives → the assignment strategy finds a free spot that fits → the spot is marked taken in one atomic step → a ticket is issued.
- Exit: scan the ticket → the pricing strategy works out the fee from the time parked → payment → the spot is freed.
- Display boards subscribe to spot changes (Observer) and update their counts.
Deep dives
- Concurrency: two gates assigning the same spot. Use a (per floor, or for the whole lot), or an atomic on the spot.
- Fit rules: can a bike use a car spot when bike spots are full? Decide, and say it out loud.
- Lost ticket: charge the maximum daily fee.
- Extensibility: electric-car charging spots become a new spot type with their own pricing strategy.
Common mistakes
- Putting pricing logic inside Ticket or Vehicle.
- Ignoring two gates acting at the same moment.
- Vehicle that override lots of behaviour instead of just declaring their size.
from enum import Enum
from datetime import datetime
import threading, uuid
class Size(Enum):
SMALL = 1
COMPACT = 2
LARGE = 3
class Vehicle:
size = Size.COMPACT
class Car(Vehicle):
size = Size.COMPACT
class Truck(Vehicle):
size = Size.LARGE
class Spot:
def __init__(self, spot_id, size):
self.id, self.size, self.vehicle = spot_id, size, None
class Ticket:
def __init__(self, vehicle, spot):
self.id, self.vehicle, self.spot = uuid.uuid4().hex, vehicle, spot
self.entry = datetime.now()
class ParkingLot:
def __init__(self, spots, pricing):
self.spots, self.pricing = spots, pricing
self._lock = threading.Lock()
def park(self, vehicle):
with self._lock: # no two vehicles get the same spot
spot = next((s for s in self.spots
if s.vehicle is None and s.size.value >= vehicle.size.value), None)
if spot is None:
raise RuntimeError("car park full")
spot.vehicle = vehicle
return Ticket(vehicle, spot)
def leave(self, ticket):
hours = (datetime.now() - ticket.entry).total_seconds() / 3600
fee = self.pricing.fee(hours)
ticket.spot.vehicle = None
return feeStep 2 Go further (free, optional)
Optional. The notes above are written to be enough on their own. These free resources give a second explanation and extra practice.
-
Watch
YouTube
-
Code
Awesome Low-Level Design (questions and solutions)
GitHub, free
Step 3 Practise
Try each exercise yourself first: on paper, out loud or in code. Then open the answer and compare it with yours.
-
ExplainSay it in plain words in under a minute, with no notes.
Explain your class design in 2 minutes, naming the patterns you used.
Show answer
"A ParkingLot has several Floors, and each Floor has Spots of three sizes: small, compact and large. A Vehicle is abstract; Bike, Car and Truck each say which spot size they need. When a vehicle arrives, the lot asks a SpotAssignmentStrategy for a free spot that fits, for example the nearest one, marks it taken and issues a Ticket with the entry time. On exit, a PricingStrategy works out the fee from the time parked, a Payment is taken, and the spot is freed.
The patterns, and why: Strategy for spot assignment and for pricing, because those are the rules most likely to change (nearest-first, weekend rates, EV charging). A Factory creates vehicles and spots from input. Display boards are Observers of spot changes, so they update their free counts without the lot knowing about them. There is one ParkingLot, created at start-up and injected.
The key risk is concurrency: two gates assigning the same spot. Finding and taking a spot happens in one atomic step under a lock. To extend it, an EV charging spot is a new spot type with its own pricing strategy; nothing else changes."
-
DrawSketch the diagram (or class diagram) from memory.
Draw the with relationships and multiplicities.
Show answer
(the filled diamond) marks parts that belong to one whole: a lot's floors, a floor's spots. A Spot holds at most one Vehicle, and a Ticket links one Vehicle to one Spot. The two strategies are the lot holds, so they can be swapped.
textParkingLot + park(vehicle): Ticket + leave(ticket): fee - assigner: SpotAssignmentStrategy ──► «interface» - pricing: PricingStrategy ──┐ + find_spot(floors, vehicle) │ 1 │ △ NearestFirst ◆ └──► «interface» │ 1..* + fee(hours) Floor △ Hourly, Weekend + free_count(size) │ 1 ◆ │ 1..* Spot ── 0..1 ── Vehicle «abstract» id, size, + size vehicle △ Bike, Car, Truck ▲ 1 │ Ticket ── 1 ── Vehicle id, entry time Payment ── 1 ── Ticket (amount, method, status) DisplayBoard ┄┄ observes ┄┄► Floor -
ApplyUse it in a real design question and say exactly where it fits.
Complete the code with a PricingStrategy (first hour £2, then £1.50 per hour) and test parking 3 vehicles.
Show answer
The pricing strategy charges £2 for the first hour, then £1.50 for each extra hour or part of one, all in pence to keep money exact. A fake clock makes the fees testable without waiting hours.
Three vehicles park in three spots of the right size; a fourth gets "car park full". The bike leaves after 45 minutes and pays £2.00; the car leaves after two and a half hours (3 started hours) and pays £2 + 2 × £1.50 = £5.00. A new car then gets the compact spot the old car freed, not the bike's small spot.
pythonimport math import threading import uuid from enum import Enum class Size(Enum): SMALL = 1 COMPACT = 2 LARGE = 3 class Vehicle: size = Size.COMPACT class Bike(Vehicle): size = Size.SMALL class Car(Vehicle): size = Size.COMPACT class Truck(Vehicle): size = Size.LARGE class Spot: def __init__(self, spot_id, size): self.id, self.size, self.vehicle = spot_id, size, None class Ticket: def __init__(self, vehicle, spot, entry): self.id, self.vehicle, self.spot, self.entry = uuid.uuid4().hex, vehicle, spot, entry class PricingStrategy: def fee_pence(self, hours): raise NotImplementedError class HourlyPricing(PricingStrategy): """£2 for the first hour, then £1.50 for each extra hour or part of one.""" def fee_pence(self, hours): started_hours = max(1, math.ceil(hours)) return 200 + 150 * (started_hours - 1) class ParkingLot: def __init__(self, spots, pricing, clock): self.spots, self.pricing, self.clock = spots, pricing, clock self._lock = threading.Lock() def park(self, vehicle): with self._lock: # find and take a spot in one step spot = next((s for s in self.spots if s.vehicle is None and s.size.value >= vehicle.size.value), None) if spot is None: raise RuntimeError("car park full") spot.vehicle = vehicle return Ticket(vehicle, spot, self.clock()) def leave(self, ticket): hours = (self.clock() - ticket.entry) / 3600 fee = self.pricing.fee_pence(hours) with self._lock: ticket.spot.vehicle = None return fee now = [0] # a fake clock, in seconds lot = ParkingLot([Spot("S1", Size.SMALL), Spot("C1", Size.COMPACT), Spot("L1", Size.LARGE)], HourlyPricing(), clock=lambda: now[0]) tickets = [lot.park(vehicle) for vehicle in (Bike(), Car(), Truck())] print([t.spot.id for t in tickets]) # ['S1', 'C1', 'L1'] try: lot.park(Car()) except RuntimeError as error: print(error) # car park full now[0] = 45 * 60 # 45 minutes after entry print(lot.leave(tickets[0])) # 200: the first hour now[0] = int(2.5 * 3600) # 2.5 hours after entry print(lot.leave(tickets[1])) # 500: £2 + 2 × £1.50 print(lot.park(Car()).spot.id) # C1: the bike's spot is too small -
Trade-offsSay what it costs, when not to use it, and the alternative.
One lock for the whole car park, one per floor, or a compare-and-set per spot: compare them.
Show answer
• One lock for the whole car park: the simplest, and obviously correct. Every gate waits its turn to assign a spot, but assigning takes microseconds and a car park has a handful of gates, so the waiting is negligible. The right default.
• One lock per floor: gates on different floors can assign at the same time. But a vehicle that doesn't fit on one floor must move on to the next, and taking locks in different orders invites , so you need a fixed lock order. More complexity, for contention that a real car park doesn't have.
• Compare-and-set per spot: no locks at all. Each gate scans for a free spot and claims it only if it is still free ("set vehicle if currently empty", atomically); if another gate won, it the next spot. The most concurrent, but subtler: , and it needs an atomic primitive, which in a distributed system is a conditional database update.In the interview: start with one lock and say why it is enough. If the gates are separate machines, the lock moves into the database as a conditional update (UPDATE spots SET vehicle = … WHERE id = … AND vehicle IS NULL), which is really compare-and-set per spot.
Step 4 Interview-ready, then prove it
Your interview answer
About 20 to 40 seconds, said out loud. Practise it until it sounds like you, not like notes.
Core classes: ParkingLot, Floor, Spot, the Vehicle types, Ticket and Payment. Spot assignment and pricing are strategies, so the rules can change without touching the rest. Parking is one atomic step — a lock or a compare-and-set — so two gates can't take the same spot. Display boards observe spot changes.
Follow-up questions
How would this work with gates on different servers?
Keep spot state in a database and claim a spot with a conditional update (UPDATE … WHERE spot_id = ? AND vehicle_id IS NULL), trying another spot if no row changed.
How would you add electric-car charging?
A new spot type with a charger, plus a pricing strategy that adds the energy cost. No existing classes change.
Tick each check when you can do it without notes
A topic is finished when you pass all four checks, not when you finish a video. Be strict: if you needed your notes, it is not ticked yet.
- Explain Say it in plain words in under a minute, with no notes.
- Draw Sketch the diagram (or class diagram) from memory.
- Apply Use it in a real design question and say exactly where it fits.
- Trade-offs Say what it costs, when not to use it, and the alternative.
0 of 4 checks ticked.
Saved in this browser. Back it up or move it to another device.