"Design a Parking Lot."
Almost every SDE-I/II candidate hits this question at some point. It sounds easy — until the interviewer adds: multiple floors, different vehicle types, different spot sizes, dynamic pricing, and a live display board. Suddenly "just write a ParkingLot class" falls apart, and this is exactly the problem where combining SOLID and multiple patterns pays off the most.
Clarifying questions
- How many floors, and do spots differ in size (motorcycle, compact, large)?
- Is pricing flat, hourly, or does it vary by vehicle type?
- Do we need real-time availability display?
- Is concurrent access (multiple entry gates) in scope?
Assume: multiple floors, three spot sizes, at least two pricing schemes, a live display, and yes — concurrency matters.
Finding the objects
- ParkingLot — the top-level coordinator: owns floors, issues tickets.
- ParkingFloor — owns a set of spots on one level.
- ParkingSpot — one physical spot; knows its size and whether it's free.
- Vehicle (and
Car,Bike,Truck) — what gets parked. - ParkingTicket — issued on entry, closed on exit; owns duration calculation.
- PricingStrategy — how a ticket's fee is calculated.
- ParkingObserver — anything that needs to know about availability changes.
Pricing: a Strategy
"Pricing may vary" is precisely the Strategy signal from Lesson 5 — pull it out from the start instead of hard-coding it into ParkingLot:
class PricingStrategy:
def calculate_fee(self, ticket) -> float:
raise NotImplementedError
class HourlyPricingStrategy(PricingStrategy):
def calculate_fee(self, ticket) -> float:
return ticket.duration_hours() * 20
class FlatRateStrategy(PricingStrategy):
def calculate_fee(self, ticket) -> float:
return 50Display board: an Observer
"A display board should update in real time" is the Observer signal from Lesson 6 — and if a mobile app is added later, ParkingLot shouldn't need to change at all:
class ParkingObserver:
def update(self, floor_id: str, free_spots: int):
raise NotImplementedError
class DisplayBoard(ParkingObserver):
def update(self, floor_id: str, free_spots: int):
print(f"Floor {floor_id}: {free_spots} spots free")Creating vehicles: a Factory
Entry-gate code needs to turn a type string into the right Vehicle subclass — the exact Factory signal from Lesson 4:
class VehicleFactory:
@staticmethod
def create(vehicle_type: str, plate: str) -> "Vehicle":
if vehicle_type == "car":
return Car(plate)
elif vehicle_type == "bike":
return Bike(plate)
elif vehicle_type == "truck":
return Truck(plate)
raise ValueError(f"Unknown vehicle type: {vehicle_type}")Putting it together
class ParkingSpot:
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size # "motorcycle" | "compact" | "large"
self.is_free = True
self.lock = threading.Lock() # per-spot lock — see Concurrency below
class ParkingFloor:
def __init__(self, floor_id, spots):
self.floor_id = floor_id
self.spots = spots
def find_free_spot(self, size):
return next((s for s in self.spots if s.is_free and s.size == size), None)
def free_spot_count(self):
return sum(1 for s in self.spots if s.is_free)
class ParkingLot:
def __init__(self, floors, pricing: PricingStrategy):
self.floors = floors
self.pricing = pricing
self._observers = []
def attach(self, observer: ParkingObserver):
self._observers.append(observer)
def _notify(self, floor: ParkingFloor):
for obs in self._observers:
obs.update(floor.floor_id, floor.free_spot_count())
def park_vehicle(self, vehicle, size) -> "ParkingTicket":
for floor in self.floors:
spot = floor.find_free_spot(size)
if spot:
with spot.lock: # atomic check-and-claim
if not spot.is_free:
continue # lost the race, try next
spot.is_free = False
ticket = ParkingTicket(spot, vehicle)
self._notify(floor)
return ticket
raise Exception("Parking lot full")
def unpark(self, ticket: "ParkingTicket") -> float:
ticket.close()
fee = self.pricing.calculate_fee(ticket)
ticket.spot.is_free = True
return feeSequence: parking a car
Concurrency: the double-booking race
is_free == True before either sets it to False — a classic check-then-act race condition. A lock per spot (as in the code above) closes that gap; at larger scale, an atomic compare-and-swap on a distributed store does the same job.What if the interviewer changes this?
Vehicle subclass, one new branch in VehicleFactory — ParkingLot and ParkingFloor never change.ParkingObserver, plus one attach() call — ParkingLot never changes.Common mistakes
- Folding pricing, payment, and display logic all into
ParkingLot(a God class). - Hard-coding vehicle types with if/else scattered at every entry point instead of a Factory.
- Ignoring the concurrency follow-up entirely, or handling it with an unsynchronized check-then-set.
- Forgetting to remove/re-add observers if display hardware is swapped or restarted (see the memory-leak trap from Lesson 6).
Quick recap
| Class / pattern | Job |
|---|---|
| ParkingLot | Coordinates floors, issues/closes tickets, holds pricing + observers. |
| ParkingFloor / ParkingSpot | Own spot inventory and free/occupied state. |
| VehicleFactory | Creates the right Vehicle subclass from a type string. |
| PricingStrategy | Swappable fee calculation. |
| ParkingObserver | Notified on availability changes — display, app, dashboard. |
One thing to remember
Three different questions — which vehicle to create, which price to apply, who to notify — deserve three different patterns, each because a real, separate kind of variation exists, not because "more patterns look thorough."
Practice Zone
Five MCQs, then two reasoning questions.
Which question below actually changes the parking lot design, and should be asked early?
Asked in


Why shouldn't ParkingLot directly calculate the parking fee itself?
Asked in

What is the role of Observer in a parking lot design?
Asked in

Two cars arrive at almost the same instant and both try to park in the last free spot. What must the design guarantee?
Asked in


A candidate's ParkingLot class handles parking, unparking, fee calculation, payment processing, and display updates, all in one class. What's the issue?
Asked in


Think it through, then reveal:
Walk through why a parking lot design ends up using Factory (for vehicles), Strategy (for pricing) and Observer (for the display board) — three different patterns in the same problem. Isn't that overkill?
Asked in


Why is a simple if spot.is_free: spot.is_free = False check-then-set not safe for concurrent parking requests, and what's a cleaner fix?
Asked in

FAQ
Should ParkingLot be a Singleton?
Reasonable, if the requirement says there's exactly one central coordinator for the whole facility — this matches Lesson 3's "when Singleton is the right call" criteria. For a chain with multiple physical lots, you'd more likely have one instance per lot, managed by a separate registry, rather than one giant global Singleton.
How do I decide spot size matching (a bike parking in a large spot)?
A common, simple rule: search same-size spots first, then fall back to larger sizes if none are free (a bike can use a compact or large spot if no motorcycle spot is free; a truck can only use a large spot). State this rule explicitly rather than leaving it ambiguous.
Do I need a full database schema for this in the interview?
No — LLD interviews expect in-memory class design with working logic, not a persistence layer. If asked about persistence, a one-line mention ("ParkingTicket and spot state would be backed by a database in production, with the same class shapes") is usually enough.
Next, a system where two members racing for the same book is the new version of the double-booking race — Lesson 11: Design a Library Management System →


