cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 185 of 218
Ideas this builds on
Low-level design185 / 218

Design a parking lot

Picture

A vehicle arrives, claims a fitting free spot, and receives a ticket. On exit, that exact ticket releases that exact stay. Begin with the two invariants: one active vehicle per spot, one active ticket per vehicle.

Predict

What must remain unchanged if no fitting spot is available?

Walk through the small example

All spots and active tickets. A failed allocation should not issue a ticket or partially claim resources.

Mental model and complete Python solution

Spot holds fit and occupancy data; immutable Ticket identifies a stay; ParkingLot coordinates them. The policy chooses the smallest fitting free spot and breaks ties by spot ID. Track both tickets and vehicles to reject duplicate active parking. Exit removes a ticket once; an old ticket cannot later release a new stay in the same spot. This is a single-threaded simulator; a concurrent implementation must make validation and claiming atomic.

What the function promises: Vehicle and spot sizes are 1=small, 2=medium, 3=large. A larger spot can fit a smaller vehicle. park returns Ticket or None if full; duplicate active vehicles raise. leave returns whether an active ticket was closed. Payment, reservations, databases, and multiple gates are intentionally out of scope.

from dataclasses import dataclass
from typing import Optional

@dataclass
class Spot:
    id: int
    size: int
    vehicle: Optional[str] = None

    def fits(self, size):
        return self.vehicle is None and size <= self.size

@dataclass(frozen=True)
class Ticket:
    id: int
    vehicle: str
    spot: int

class ParkingLot:
    def __init__(self, spots):
        spots = list(spots)
        if len({s.id for s in spots}) != len(spots) or any(
                s.size not in (1,2,3) or s.vehicle is not None for s in spots):
            raise ValueError("Need unique, initially free, valid spots")
        self.spots = {s.id: Spot(s.id, s.size) for s in spots}
        self.tickets, self.vehicles = {}, {}
        self.next_ticket = 1

    def park(self, vehicle, size):
        if size not in (1,2,3) or not vehicle:
            raise ValueError("Invalid vehicle")
        if vehicle in self.vehicles:
            raise ValueError("Vehicle is already parked")
        candidates = [s for s in self.spots.values() if s.fits(size)]
        if not candidates:
            return None
        spot = min(candidates, key=lambda s: (s.size, s.id))
        ticket = Ticket(self.next_ticket, vehicle, spot.id)
        self.next_ticket += 1
        spot.vehicle = vehicle
        self.tickets[ticket.id] = ticket
        self.vehicles[vehicle] = ticket.id
        return ticket

    def leave(self, ticket_id):
        ticket = self.tickets.pop(ticket_id, None)
        if ticket is None:
            return False
        self.spots[ticket.spot].vehicle = None
        del self.vehicles[ticket.vehicle]
        return True

lot = ParkingLot([Spot(1, 1), Spot(2, 3)])
a = lot.park("bike", 1)
b = lot.park("car", 2)
assert (a.spot, b.spot) == (1, 2)
assert lot.park("truck", 3) is None
assert lot.leave(b.id)
c = lot.park("truck", 3)
assert not lot.leave(b.id) and lot.spots[c.spot].vehicle == "truck"

Work and storage: O(S) allocation over S spots and expected O(1) exit lookups. Storage is O(S+active tickets). A free-spot index is an optimization to introduce only if these scans matter.

When to recognize it: Start with one vehicle per spot and one active ticket per vehicle. Let one service coordinate both facts.

Check before applying it: Multiple gates require an atomic allocation boundary. Payment, reservations, and durable recovery are outside this first in-memory design.

Change one rule

Add reservations and multiple entrance gates. Which boundary becomes more important than class naming?

See what changes

Atomic allocation across available, reserved, and occupied states. A shared database transaction or equivalent conditional claim must ensure only one caller wins; a check in one process alone is insufficient.

Connect

Objects and responsibilities: Spots and tickets have different responsibilities.

Make failure and retries part of the design: Protect check-and-claim when concurrent requests arrive.