Courses › Low-Level Design › Problem Walkthroughs
Design a Parking Lot
Entities, spot allocation strategies, tickets and pricing.
"Design a parking lot" is the most classic LLD question. It's simple to understand, which leaves room to show good modeling, a clean use of the Strategy pattern, and attention to concurrency.
Requirements
Clarify first. A reasonable scope:
- The lot has multiple levels, each with spots of different sizes: small (motorcycle), medium (car), large (truck/van)
- A vehicle enters: the system finds a suitable free spot and issues a ticket
- A vehicle exits: the system computes the fee based on duration, frees the spot
- Show availability per level
Out of scope: reservations, payment processing, multiple entrances (mention as extensions).
Entities
Fitting rules
A vehicle fits in a spot of its size or larger. Encode it on the enum's ordering so the rule lives in one place.
Implementation
from dataclasses import dataclass, field
from datetime import datetime
from enum import IntEnum
from threading import Lock
import itertools, math
class VehicleSize(IntEnum):
SMALL = 1
MEDIUM = 2
LARGE = 3
@dataclass(frozen=True)
class Vehicle:
plate: str
size: VehicleSize
class ParkingSpot:
def __init__(self, spot_id: str, size: VehicleSize):
self.id = spot_id
self.size = size
self.vehicle = None
def can_fit(self, vehicle: Vehicle) -> bool:
return self.vehicle is None and vehicle.size <= self.size
def assign(self, vehicle: Vehicle):
self.vehicle = vehicle
def release(self):
self.vehicle = None
class Level:
def __init__(self, number: int, spots: list[ParkingSpot]):
self.number = number
self.spots = spots
def free_spots(self, vehicle: Vehicle):
return [s for s in self.spots if s.can_fit(vehicle)]
@dataclass
class Ticket:
id: int
vehicle: Vehicle
spot: ParkingSpot
entry_time: datetime
exit_time: datetime | None = None
class AllocationStrategy:
def choose(self, levels: list[Level], vehicle: Vehicle) -> ParkingSpot | None:
raise NotImplementedError
class SmallestFitLowestLevel(AllocationStrategy):
"""Prefer the lowest level, and the smallest spot that fits, to keep big spots free."""
def choose(self, levels, vehicle):
for level in levels:
spots = level.free_spots(vehicle)
if spots:
return min(spots, key=lambda s: s.size)
return None
class PricingStrategy:
def fee_cents(self, ticket: Ticket) -> int:
raise NotImplementedError
class HourlyPricing(PricingStrategy):
RATES = {VehicleSize.SMALL: 200, VehicleSize.MEDIUM: 400, VehicleSize.LARGE: 800}
def fee_cents(self, ticket):
minutes = (ticket.exit_time - ticket.entry_time).total_seconds() / 60
hours = max(1, math.ceil(minutes / 60))
return hours * self.RATES[ticket.vehicle.size]
class LotFullError(Exception):
pass
class ParkingLot:
def __init__(self, levels, allocation: AllocationStrategy, pricing: PricingStrategy, clock=datetime.now):
self.levels = levels
self.allocation = allocation
self.pricing = pricing
self.clock = clock
self.active = {} # ticket id -> Ticket
self._ids = itertools.count(1)
self._lock = Lock() # spot assignment must be atomic
def enter(self, vehicle: Vehicle) -> Ticket:
with self._lock:
spot = self.allocation.choose(self.levels, vehicle)
if spot is None:
raise LotFullError(f"No spot for {vehicle.size.name}")
spot.assign(vehicle)
ticket = Ticket(next(self._ids), vehicle, spot, self.clock())
self.active[ticket.id] = ticket
return ticket
def exit(self, ticket_id: int) -> int:
with self._lock:
ticket = self.active.pop(ticket_id)
ticket.exit_time = self.clock()
ticket.spot.release()
return self.pricing.fee_cents(ticket)Design decisions to explain
- Strategies for allocation and pricing — both are likely to change (EV spots, weekend rates), so they're injected interfaces. *Open/closed* and *dependency inversion*.
ParkingLotas a facade — callers have two operations; internals can evolve.- Injected clock — makes pricing testable.
- Locking — without the lock, two threads could choose the same free spot. A single lock is simple and fine for one lot; for higher throughput, lock per level or use atomic compare-and-set on each spot.
Extensions to discuss
- Multiple entrances — each entrance runs
enterconcurrently; the lock (or per-spot atomic assignment) already handles it. - EV charging spots — add a spot feature set and a strategy that prefers charging spots for EVs.
- Availability display — an
Observerthat updates level counters on assign/release, instead of recounting. - Reservations — a
Reservationentity that holds a spot for a time window. - Persistence — tickets stored in a repository interface so the lot survives restarts.