Low-Level Design · lesson 5 of 18
Beginner
4Intermediate
10- 5 Design a Parking Lot: Allocating and Freeing Spots
- 6 Design an Elevator System: Scheduling Requests
- 7 Design a Library Management System: Borrowing and Returning Books
- 8 Design an Expense-Splitting App: Balances and Minimal Settlements
- 9 Design a URL Shortener: Sequential Codes vs Real Hash Collisions
- 10 Wrapping Objects: Adapter, Facade, and Proxy in Python
- 11 The Composite Pattern in Python: Sizing a File Tree Without isinstance
- 12 The State Pattern in Python: Refactoring a Vending Machine Into State Classes
- 13 Undo and Redo in Python: the Memento and Command Patterns Compared
- 14 Chain of Responsibility in Python: Expense Approvals Without an if/elif Ladder
Advanced
4- 15 Concurrency in LLD: A Real Race Condition in the Parking Lot, Then the Fix
- 16 The Observer Pattern in Practice: A Stock Ticker Notifying Multiple Displays
- 17 Design Patterns That Are Just Language Features: Strategy, Command, Iterator
- 18 Abstract Factory and Prototype in Python: Matching Families and Cloning Objects
Also part of Low-Level Design
12- + SOLID Principles in Java from Java
- + Composition vs Inheritance in Java from Java
- + Design Patterns in Java from Java
- + Python Design Patterns from Python
- + Design Patterns in TypeScript from TypeScript
- + JavaScript Design Patterns from JavaScript
- + Design Patterns in C# from C#
- + Design Patterns in C++ from C++
- + Design Patterns in Go from Go
- + Design Patterns in Rust from Rust
- + Abstract Classes in Java from Java
- + Sealed Classes and Interfaces in Java from Java
Design a Parking Lot: Allocating and Freeing Spots
A motorcycle, a car, and a bus compete for three spot sizes — best-fit allocation, a full-lot rejection, and freeing a spot back for reuse.
By SkillByExample · Published
Three vehicle types, three spot sizes, and a rule about which vehicle fits which spot — the first real “design a system” problem in this track.
Spots, vehicles, and best-fit allocation
class ParkingSpot:
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size # "small", "medium", "large"
self.vehicle = None
@property
def is_free(self):
return self.vehicle is None
SIZE_RANK = {"small": 1, "medium": 2, "large": 3}
VEHICLE_MIN_SIZE = {"motorcycle": "small", "car": "medium", "bus": "large"}
class ParkingLot:
def __init__(self, spots):
self.spots = spots
def allocate(self, vehicle_id, vehicle_type):
min_size = VEHICLE_MIN_SIZE[vehicle_type]
candidates = [
s for s in self.spots
if s.is_free and SIZE_RANK[s.size] >= SIZE_RANK[min_size]
]
if not candidates:
print(f"No spot available for {vehicle_type} {vehicle_id}")
return None
best = min(candidates, key=lambda s: SIZE_RANK[s.size])
best.vehicle = vehicle_id
print(f"{vehicle_id} ({vehicle_type}) parked at spot {best.spot_id} ({best.size})")
return best.spot_id
def free(self, spot_id):
for s in self.spots:
if s.spot_id == spot_id:
if s.is_free:
print(f"Spot {spot_id} is already free")
return
print(f"Spot {spot_id} freed (was {s.vehicle})")
s.vehicle = None
return
print(f"No such spot {spot_id}")
lot = ParkingLot([
ParkingSpot(1, "small"),
ParkingSpot(2, "medium"),
ParkingSpot(3, "large"),
])
lot.allocate("bike-1", "motorcycle")
lot.allocate("car-1", "car")
lot.allocate("bus-1", "bus")
lot.allocate("car-2", "car")
lot.free(2)
lot.allocate("car-2", "car")
$ python parking_lot.py
bike-1 (motorcycle) parked at spot 1 (small)
car-1 (car) parked at spot 2 (medium)
bus-1 (bus) parked at spot 3 (large)
No spot available for car car-2
Spot 2 freed (was car-1)
car-2 (car) parked at spot 2 (medium)
car-2’s first attempt fails outright — every spot compatible with a car (medium or large)
is already taken, and the lot correctly refuses rather than bumping anyone. Freeing spot 2
makes it available again, and the next identical allocation call succeeds this time.
Practice
1. Confirm a motorcycle falls back to a large spot when nothing smaller is free.
class ParkingSpot:
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size
self.vehicle = None
@property
def is_free(self):
return self.vehicle is None
SIZE_RANK = {"small": 1, "medium": 2, "large": 3}
VEHICLE_MIN_SIZE = {"motorcycle": "small", "car": "medium", "bus": "large"}
class ParkingLot:
def __init__(self, spots):
self.spots = spots
def allocate(self, vehicle_id, vehicle_type):
min_size = VEHICLE_MIN_SIZE[vehicle_type]
candidates = [s for s in self.spots if s.is_free and SIZE_RANK[s.size] >= SIZE_RANK[min_size]]
if not candidates:
print(f"No spot available for {vehicle_type} {vehicle_id}")
return None
best = min(candidates, key=lambda s: SIZE_RANK[s.size])
best.vehicle = vehicle_id
print(f"{vehicle_id} ({vehicle_type}) parked at spot {best.spot_id} ({best.size})")
return best.spot_id
lot = ParkingLot([ParkingSpot(1, "large")])
lot.allocate("bike-1", "motorcycle")
$ python fallback.py
bike-1 (motorcycle) parked at spot 1 (large)
SIZE_RANK[s.size] >= SIZE_RANK[min_size] accepts any spot at least as big as required, so a
motorcycle (needs small) matches a large spot fine when it’s the only one left.
2. Add an available_count method reporting free spots by size.
class ParkingSpot:
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size
self.vehicle = None
@property
def is_free(self):
return self.vehicle is None
class ParkingLot:
def __init__(self, spots):
self.spots = spots
def available_count(self):
counts = {}
for s in self.spots:
if s.is_free:
counts[s.size] = counts.get(s.size, 0) + 1
return counts
lot = ParkingLot([
ParkingSpot(1, "small"), ParkingSpot(2, "small"),
ParkingSpot(3, "medium"), ParkingSpot(4, "large"),
])
lot.spots[0].vehicle = "bike-1"
print(f"Available: {lot.available_count()}")
$ python available_count.py
Available: {'small': 1, 'medium': 1, 'large': 1}
Only one small spot shows as available — the other was occupied directly for this example
— confirming the count reflects live is_free state, not the lot’s total capacity.
3. Confirm freeing a spot ID that doesn't exist fails cleanly.
class ParkingSpot:
def __init__(self, spot_id, size):
self.spot_id = spot_id
self.size = size
self.vehicle = None
@property
def is_free(self):
return self.vehicle is None
class ParkingLot:
def __init__(self, spots):
self.spots = spots
def free(self, spot_id):
for s in self.spots:
if s.spot_id == spot_id:
if s.is_free:
print(f"Spot {spot_id} is already free")
return
s.vehicle = None
print(f"Spot {spot_id} freed")
return
print(f"No such spot {spot_id}")
lot = ParkingLot([ParkingSpot(1, "small")])
lot.free(99)
$ python bad_free.py
No such spot 99
The for loop falls through to the trailing print when no spot matches — there’s no
IndexError or silent no-op, because free never assumes the spot ID it’s given is valid.
Next: an elevator that has to schedule floor requests, not just move up and down on command.