LLD: Parking Lot
Levels, spot sizes, tickets, and fees — the classic LLD warm-up, with spot allocation and pricing as swappable strategies.
Big idea
A parking lot is a set of typed spots, a ticket per stay, and a fee rule. The design's quality shows in two places: which spots a vehicle may use (a small rule that's easy to scatter) and how fees are computed (the thing that always changes). Put both behind clear owners and the rest is bookkeeping.
Requirements
Functional
- Multiple levels; each has spots of sizes small (motorbike), compact (car), large (van/bus).
- A vehicle may park in a spot of its size or larger.
- Entry issues a ticket; exit computes a fee from duration and vehicle type, and frees the spot.
- Show free spots per level and size.
Out of scope (say so): payments, reservations, multiple entrances racing for the same spot (we'll discuss concurrency at the end).
Entities
ParkingLot, Level, Spot (with SpotSize), Vehicle (with VehicleType), Ticket, FeePolicy (interface), SpotFinder (allocation rule). Time comes from a Clock so tests can control it.
Think of it like
A cloakroom. Each item gets a numbered tag (ticket) and a hook that fits it (spot — coats on hooks, suitcases on the floor). When you hand the tag back, the attendant fetches your item and charges by how long you left it.
Class diagram
Key flow: entry and exit
Code skeleton
from enum import IntEnum
import math
class Size(IntEnum): # ordered, so "this size or larger" is a comparison
SMALL = 1
COMPACT = 2
LARGE = 3
VEHICLE_SIZE = {"bike": Size.SMALL, "car": Size.COMPACT, "van": Size.LARGE}
class Spot:
def __init__(self, sid, size):
self.id, self.size, self.vehicle = sid, size, None
def is_free(self):
return self.vehicle is None
class Level:
def __init__(self, number, sizes):
self.number = number
self.spots = [Spot(f"L{number}-{i}", s) for i, s in enumerate(sizes)]
def free_spot(self, needed): # smallest free spot that fits: keeps big spots for big vehicles
fits = [s for s in self.spots if s.is_free() and s.size >= needed]
return min(fits, key=lambda s: s.size, default=None)
class HourlyFee: # a FeePolicy: swap it without touching ParkingLot
RATES = {"bike": 1, "car": 3, "van": 6}
def fee(self, ticket, now):
hours = max(1, math.ceil((now - ticket["at"]) / 60))
return hours * self.RATES[ticket["type"]]
class ParkingLot:
def __init__(self, levels, fee_policy, clock):
self.levels, self.fees, self.clock = levels, fee_policy, clock
self.active, self.next_id = {}, 1
def enter(self, plate, vtype):
needed = VEHICLE_SIZE[vtype]
for level in self.levels:
spot = level.free_spot(needed)
if spot:
spot.vehicle = plate
ticket = {"id": self.next_id, "spot": spot, "type": vtype, "at": self.clock()}
self.active[self.next_id] = ticket
self.next_id += 1
return ticket
return None # lot full for this size
def exit(self, ticket_id):
ticket = self.active.pop(ticket_id)
ticket["spot"].vehicle = None
return self.fees.fee(ticket, self.clock())
def availability(self):
return {f"L{l.number}": sum(s.is_free() for s in l.spots) for l in self.levels}
now = [0] # a fake clock, in minutes
lot = ParkingLot([Level(0, [Size.SMALL, Size.COMPACT]), Level(1, [Size.COMPACT, Size.LARGE])],
HourlyFee(), clock=lambda: now[0])
t1 = lot.enter("BIKE-1", "bike")
t2 = lot.enter("CAR-1", "car")
t3 = lot.enter("CAR-2", "car")
t4 = lot.enter("VAN-1", "van")
print([t["spot"].id for t in (t1, t2, t3, t4)])
print("another van:", lot.enter("VAN-2", "van"))
now[0] = 130 # 2h10m later
print("car 1 pays", lot.exit(t2["id"]), "| van pays", lot.exit(t4["id"]))
print("free:", lot.availability())output
['L0-0', 'L0-1', 'L1-0', 'L1-1']
another van: None
car 1 pays 9 | van pays 18
free: {'L0': 1, 'L1': 1}Run and edit it in the app.
Notice the allocation rule — "smallest spot that fits" — lives in one method. Change it to "nearest the exit" and nothing else moves.
Watch it
The business adds weekend flat rates and a first-30-minutes-free promotion. Where should that change go?
Two entry gates call enter() at the same moment and both pick the last free compact spot. What's the simplest fix?
Extensions and trade-offs
- New vehicle types (electric cars needing chargers): add a spot feature, not a new size; the finder filters by features.
- Display boards: Observer — levels publish "spot freed/taken" events.
- Reservations: a spot gains a "held until" state — see Movie ticket booking for the hold pattern.
- Concurrency: atomic claim per spot; at scale, partition by level.
- Persistence: tickets and spot state in a database; the classes stay the same behind a repository (Dependency Inversion).
Key terms
- Spot allocation — The rule for choosing which free spot a vehicle gets.
- Fee policy — The swappable pricing rule applied at exit.
- Atomic claim — Finding and taking a resource in one indivisible step so two requests can't both win.