Low-Level Design: Parking Lot System (OOP Interview)

Problem Statement

Design an object-oriented parking lot system. The lot has multiple levels, each with multiple spots of different sizes (motorcycle, compact, large). Vehicles of various sizes (motorcycle, car, truck) enter, park in the first available suitable spot, and exit with a fee calculated based on parked duration.

Class Hierarchy

from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import threading

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

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

# Vehicle hierarchy
class Vehicle:
    def __init__(self, license_plate: str, size: VehicleSize):
        self.license_plate = license_plate
        self.size = size

    def can_fit_in(self, spot_size: SpotSize) -> bool:
        # Vehicle fits if spot is >= vehicle size
        return spot_size.value >= self.size.value

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

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

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

Spot and Ticket

class ParkingSpot:
    def __init__(self, level: int, spot_id: int, size: SpotSize):
        self.level    = level
        self.spot_id  = spot_id
        self.size     = size
        self.vehicle: Optional[Vehicle] = None

    @property
    def is_available(self) -> bool:
        return self.vehicle is None

    def park(self, vehicle: Vehicle) -> bool:
        if not self.is_available or not vehicle.can_fit_in(self.size):
            return False
        self.vehicle = vehicle
        return True

    def remove_vehicle(self) -> Optional[Vehicle]:
        v = self.vehicle
        self.vehicle = None
        return v

@dataclass
class Ticket:
    ticket_id:   str
    vehicle:     Vehicle
    spot:        ParkingSpot
    entry_time:  datetime = field(default_factory=datetime.now)
    exit_time:   Optional[datetime] = None

    def calculate_fee(self, hourly_rates: dict[VehicleSize, float]) -> float:
        if self.exit_time is None:
            self.exit_time = datetime.now()
        duration_hours = (self.exit_time - self.entry_time).total_seconds() / 3600
        rate = hourly_rates.get(self.vehicle.size, 5.0)
        return round(duration_hours * rate, 2)

Level and Parking Lot

class Level:
    def __init__(self, level_id: int, spots_config: dict[SpotSize, int]):
        # spots_config = {SpotSize.MOTORCYCLE: 20, SpotSize.COMPACT: 30, SpotSize.LARGE: 10}
        self.level_id = level_id
        self.spots: list[ParkingSpot] = []
        spot_id = 0
        for size, count in spots_config.items():
            for _ in range(count):
                self.spots.append(ParkingSpot(level_id, spot_id, size))
                spot_id += 1

    def find_available_spot(self, vehicle: Vehicle) -> Optional[ParkingSpot]:
        for spot in self.spots:
            if spot.is_available and vehicle.can_fit_in(spot.size):
                return spot
        return None

    def available_count(self, size: SpotSize) -> int:
        return sum(1 for s in self.spots if s.size == size and s.is_available)


class ParkingLot:
    _instance = None
    _lock = threading.Lock()

    def __new__(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super().__new__(cls)
                cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self.levels: list[Level] = []
        self.active_tickets: dict[str, Ticket] = {}  # license_plate -> Ticket
        self.hourly_rates = {
            VehicleSize.MOTORCYCLE: 2.0,
            VehicleSize.COMPACT:    4.0,
            VehicleSize.LARGE:      6.0,
        }
        self._ticket_counter = 0
        self._lock = threading.Lock()
        self._initialized = True

    def add_level(self, level: Level):
        self.levels.append(level)

    def _generate_ticket_id(self) -> str:
        self._ticket_counter += 1
        return f"TKT-{self._ticket_counter:06d}"

    def park_vehicle(self, vehicle: Vehicle) -> Optional[Ticket]:
        with self._lock:
            if vehicle.license_plate in self.active_tickets:
                print(f"{vehicle.license_plate} already parked.")
                return None

            for level in self.levels:
                spot = level.find_available_spot(vehicle)
                if spot:
                    spot.park(vehicle)
                    ticket = Ticket(
                        ticket_id=self._generate_ticket_id(),
                        vehicle=vehicle,
                        spot=spot,
                    )
                    self.active_tickets[vehicle.license_plate] = ticket
                    print(f"Parked {vehicle.license_plate} at level {spot.level}, "
                          f"spot {spot.spot_id} ({spot.size.name})")
                    return ticket

            print(f"No available spot for {vehicle.license_plate}")
            return None

    def exit_vehicle(self, license_plate: str) -> float:
        with self._lock:
            ticket = self.active_tickets.pop(license_plate, None)
            if not ticket:
                print(f"No active ticket for {license_plate}")
                return 0.0
            ticket.exit_time = datetime.now()
            ticket.spot.remove_vehicle()
            fee = ticket.calculate_fee(self.hourly_rates)
            print(f"Vehicle {license_plate} exited. Fee: $" + f"{fee:.2f}")
            return fee

    def get_availability(self) -> dict:
        with self._lock:
            result = {}
            for level in self.levels:
                result[f"Level {level.level_id}"] = {
                    size.name: level.available_count(size)
                    for size in SpotSize
                }
            return result

Usage Example

def demo():
    lot = ParkingLot()

    # Configure two levels
    lot.add_level(Level(0, {SpotSize.MOTORCYCLE: 5, SpotSize.COMPACT: 10, SpotSize.LARGE: 3}))
    lot.add_level(Level(1, {SpotSize.MOTORCYCLE: 5, SpotSize.COMPACT: 10, SpotSize.LARGE: 3}))

    # Vehicles arrive
    bike  = Motorcycle("MOTO-001")
    car1  = Car("CAR-001")
    truck = Truck("TRUCK-001")

    lot.park_vehicle(bike)
    lot.park_vehicle(car1)
    lot.park_vehicle(truck)

    print(lot.get_availability())

    # Vehicles exit
    lot.exit_vehicle("CAR-001")
    lot.exit_vehicle("TRUCK-001")

demo()

Design Decisions and Trade-offs

  • Singleton ParkingLot: Thread-safe double-checked locking ensures one lot instance. In a microservice context, replace with a stateless service backed by a database.
  • Vehicle fits in larger spot: can_fit_in compares enum values numerically — a motorcycle (size=1) fits in a compact (size=2) or large (size=3) spot. This maximizes utilization at the cost of leaving large spots for trucks.
  • Spot selection strategy: The current implementation takes the first available spot (top-to-bottom). Production improvement: prefer the smallest suitable spot (don’t park a motorcycle in a large spot unless necessary). Implement by iterating spots sorted by size.
  • Thread safety: A single threading.Lock guards park_vehicle and exit_vehicle. For higher concurrency: use per-level locks, or (better) a database with row-level locking and optimistic concurrency.
  • Fee calculation: Simple hourly rate. Extensions: minimum charge (first 30 minutes free), daily maximums, validation discounts.

Extension: Reservation System

@dataclass
class Reservation:
    reservation_id: str
    vehicle_size:   VehicleSize
    start_time:     datetime
    end_time:       datetime
    spot:           Optional[ParkingSpot] = None

    def is_active(self) -> bool:
        now = datetime.now()
        return self.start_time <= now <= self.end_time

# ParkingLot.reserve_spot() would:
# 1. Find a spot not reserved for the given time window
# 2. Create a Reservation record
# 3. Return reservation_id
# On arrival: look up reservation_id, assign the pre-reserved spot

Interview Checklist

  • Clarify: number of levels, spot sizes, vehicle types, fee structure
  • Inheritance: Vehicle and Spot hierarchies with size enum comparison
  • Thread safety: lock on park/exit; discuss per-level lock for scale
  • Singleton: ParkingLot as singleton; mention stateless alternative for microservices
  • Spot selection: first-fit vs best-fit; prefer smallest fitting spot
  • Fee: time-based rate per vehicle size; handle edge cases (minimum fee, overstay)
  • Extensibility: reservation system, EV charging spots, monthly passes

🏢 Asked at: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering

🏢 Asked at: Lyft Interview Guide 2026: Rideshare Engineering, Real-Time Dispatch, and Safety Systems

🏢 Asked at: Atlassian Interview Guide

🏢 Asked at: Shopify Interview Guide

🏢 Asked at: DoorDash Interview Guide

Scroll to Top