HomeBlog › Design a Parking Lot

Design a Parking Lot

An object-oriented / low-level design interview walkthrough: the core classes, enums, the spot-assignment strategy, mixed vehicle sizes, pricing and payment, and concurrency — with clean example code.

"Design a parking lot" is the classic object-oriented design (OOD) interview question. It's deliberately different from distributed system design questions like designing WhatsApp or designing Instagram — there's no sharding or fan-out here. Instead the interviewer is judging how cleanly you model a small domain in classes, enums, and responsibilities.

Here's the full low-level parking lot walkthrough with a class map, covering requirements, the core classes, the spot-assignment strategy, mixed vehicle and spot sizes, pricing, concurrency, and the trade-offs interviewers probe.

            +----------------+
            |   ParkingLot   |  park(vehicle) / unpark(ticket)
            +--------+-------+
                     | 1..*
                     v
            +----------------+
            |     Level      |  findSpot(size)
            +--------+-------+
                     | 1..*
                     v
            +----------------+        +--------------+
            |  ParkingSpot   |<-------|    Ticket    |
            |  size, status  |        | entryTime,   |
            +----------------+        | spot, fee    |
                     ^                +--------------+
                     | fits                  ^
            +----------------+               |
            |    Vehicle     |---------------+
            | Motorcycle/Car |
            |    /Truck      |
            +----------------+
Parking lot class map: a ParkingLot owns Levels, each Level owns ParkingSpots; a Vehicle is matched to a fitting spot, and a Ticket ties the vehicle, spot, and entry time together for pricing on exit.

1. Clarify the requirements

Functional requirements

Non-functional / scope

Spend a minute agreeing on scope out loud. It's fine to say handicapped or EV-charging spots are out of scope, as long as your model leaves room to add them — that signals extensible design, which is exactly what OOD interviews reward.

A useful habit is to name the actors and the verbs before touching code. The actors are the vehicle arriving at a gate, the attendant or automated gate issuing a ticket, and the lot itself tracking occupancy. The verbs — park, unpark, find a spot, price a stay — become your methods. When you can state those in one breath, the class list almost writes itself, and you avoid the common mistake of inventing classes the problem never asked for.

2. Enums — the fixed sets

Start with enums. They pin down the closed vocabularies (sizes and statuses) and keep the rest of the code readable.

from enum import Enum

class VehicleSize(Enum):
    MOTORCYCLE = 1
    COMPACT = 2
    LARGE = 3

class SpotSize(Enum):
    MOTORCYCLE = 1
    COMPACT = 2
    LARGE = 3

class SpotStatus(Enum):
    FREE = "free"
    OCCUPIED = "occupied"

3. Core classes

Vehicle is an abstract base with concrete subclasses. Each vehicle knows the smallest spot size it needs, so the fit rule lives in one place.

class Vehicle:
    def __init__(self, plate, size):
        self.plate = plate
        self.size = size

class Motorcycle(Vehicle):
    def __init__(self, plate):
        super().__init__(plate, VehicleSize.MOTORCYCLE)

class Car(Vehicle):
    def __init__(self, plate):
        super().__init__(plate, VehicleSize.COMPACT)

class Truck(Vehicle):
    def __init__(self, plate):
        super().__init__(plate, VehicleSize.LARGE)

ParkingSpot holds a size and a status, and knows whether a given vehicle fits.

class ParkingSpot:
    def __init__(self, spot_id, size):
        self.spot_id = spot_id
        self.size = size
        self.status = SpotStatus.FREE
        self.vehicle = None

    def fits(self, vehicle):
        return self.status == SpotStatus.FREE and \
               vehicle.size.value <= self.size.value

    def assign(self, vehicle):
        self.vehicle = vehicle
        self.status = SpotStatus.OCCUPIED

    def release(self):
        self.vehicle = None
        self.status = SpotStatus.FREE

Ticket records the entry time and the assigned spot; it's the object exchanged at the exit gate.

import time

class Ticket:
    def __init__(self, ticket_id, vehicle, spot):
        self.ticket_id = ticket_id
        self.vehicle = vehicle
        self.spot = spot
        self.entry_time = time.time()
        self.exit_time = None
        self.fee = 0.0

4. Assignment strategy & the ParkingLot

The rule is simple: a vehicle can take a spot of its own size or larger. To keep assignment fast, each Level holds free spots bucketed by size, so finding a spot is a lookup, not a scan. Keeping the choice behind a small strategy method means you can later swap in nearest spot, best fit, or level balancing without rewriting the lot.

class Level:
    def __init__(self, level_id, spots):
        self.level_id = level_id
        self.spots = spots

    def find_spot(self, vehicle):
        # smallest fitting size first = best fit
        for spot in sorted(self.spots, key=lambda s: s.size.value):
            if spot.fits(vehicle):
                return spot
        return None

class ParkingLot:
    def __init__(self, levels):
        self.levels = levels
        self._next_ticket = 1

    def park(self, vehicle):
        for level in self.levels:
            spot = level.find_spot(vehicle)
            if spot:
                spot.assign(vehicle)
                ticket = Ticket(self._next_ticket, vehicle, spot)
                self._next_ticket += 1
                return ticket
        return None  # lot full for this size

    def unpark(self, ticket, pricer):
        ticket.exit_time = time.time()
        ticket.fee = pricer.price(ticket)
        ticket.spot.release()
        return ticket.fee

5. Pricing and payment

Pricing is the textbook place for the Strategy pattern. The ticket carries entry and exit timestamps; a pricing strategy turns elapsed time and vehicle type into a fee. Swapping strategies (flat hourly, tiered, daily max) never touches the parking logic.

import math

class HourlyPricer:
    RATES = {VehicleSize.MOTORCYCLE: 1.0,
             VehicleSize.COMPACT: 2.0,
             VehicleSize.LARGE: 3.5}

    def price(self, ticket):
        hours = math.ceil((ticket.exit_time - ticket.entry_time) / 3600)
        return max(1, hours) * self.RATES[ticket.vehicle.size]

Payment then simply marks the ticket paid before the gate opens. Modeling Payment as its own object (with a status and method — cash, card) keeps the door open for receipts and refunds without complicating the core flow.

6. Concurrency considerations

With several entry gates, two cars can race for the last free spot. The fix is to make spot assignment atomic: guard the free-spot collection with a lock, or use an atomic compare-and-set on each spot's status so only one gate can flip it from FREE to OCCUPIED.

import threading

class ThreadSafeLevel(Level):
    def __init__(self, level_id, spots):
        super().__init__(level_id, spots)
        self._lock = threading.Lock()

    def find_spot(self, vehicle):
        with self._lock:
            spot = super().find_spot(vehicle)
            if spot:
                spot.assign(vehicle)  # claim inside the lock
            return spot

Claiming the spot inside the lock is the key detail: it closes the check-then-act window that would otherwise let two threads see the same spot as free. Raising this unprompted is exactly the kind of low-level rigor interviewers look for.

A single global lock is simple but becomes a bottleneck in a huge lot, since every gate serializes on it. A common refinement is to lock per level, or per size bucket, so gates competing for different spot types never block each other. If asked to go further, you can mention an optimistic approach: read a candidate spot, then compare-and-set its status, retrying on the rare collision. The point is not to implement every option but to show you understand the shared-state hazard and can name proportionate fixes. This mirrors the atomic-counter reasoning in designing a rate limiter, where two requests race to decrement the same token bucket.

Key trade-offs the interviewer probes

Framework reminder: low-level design follows its own arc — requirements → core entities → enums → relationships → key methods → concurrency → trade-offs. Keep the system design cheat sheet nearby and narrate which stage you're in.

Practice object-oriented design with live AI support

CoPilot Interview surfaces a structured design skeleton — requirements, classes, methods, and trade-offs — in about 4 seconds during real Zoom and Teams calls. Free for Windows and macOS, invisible on screen-share.

Try it free

FAQ

Is designing a parking lot a system design or object-oriented design question?

It is an object-oriented (low-level) design question, not a distributed system design one. The interviewer is judging your class modeling — clean entities, enums, relationships, and responsibilities — rather than sharding, caching, or fan-out. The deliverable is a tidy set of classes and methods, not a scaled-out architecture diagram.

What are the core classes in a parking lot design?

The usual entities are ParkingLot (the top-level coordinator), Level or Floor, ParkingSpot, a Vehicle hierarchy (Motorcycle, Car, Truck), and Ticket. Enums capture the fixed sets — VehicleSize and SpotSize — and a display board or pricing component is often added. Each class owns one clear responsibility.

How do you assign a spot to a vehicle of a given size?

Each vehicle declares the smallest spot size it can occupy, and the lot searches for the nearest available spot that fits. A common rule is that a vehicle can take its own size or larger — a motorcycle fits anywhere, a car needs compact or large — and the assignment strategy can be swapped out to optimize for nearest spot, best fit, or level balancing.

How do you handle pricing and payment?

The ticket records the entry timestamp; on exit, a pricing strategy computes the fee from the elapsed duration and vehicle type. Keeping pricing behind a strategy interface lets you support flat hourly rates, tiered rates, or daily maximums without touching the parking logic, and the payment step simply marks the ticket paid before the gate opens.

Why does concurrency matter in a parking lot design?

Multiple entry gates can try to claim the last free spot at the same time, so spot assignment must be atomic. Guarding the available-spot collection with a lock, or using an atomic compare-and-set on each spot's status, prevents two cars from being assigned the same spot — the low-level equivalent of a race condition the interviewer expects you to raise.