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

Low-Level Design: Parking Lot System

The parking lot LLD is one of the most common interview questions at companies like Amazon, Google, and Microsoft. It tests state management, hierarchy modeling, and object-oriented design.

Requirements

  • Multiple floors, each with multiple spots
  • Different vehicle types: Motorcycle, Car, Truck
  • Different spot types: Compact, Regular, Large
  • A motorcycle fits in any spot; a car fits in compact or regular; a truck only in large
  • Ticket generation on entry, fee calculation on exit
  • Find available spot efficiently
  • Support for reserved/handicapped spots

Core Class Design

Enums and Vehicle Hierarchy

from enum import Enum
from datetime import datetime
from abc import ABC, abstractmethod

class VehicleType(Enum):
    MOTORCYCLE = "motorcycle"
    CAR = "car"
    TRUCK = "truck"

class SpotType(Enum):
    COMPACT = "compact"
    REGULAR = "regular"
    LARGE = "large"

class SpotStatus(Enum):
    AVAILABLE = "available"
    OCCUPIED = "occupied"
    RESERVED = "reserved"

class Vehicle(ABC):
    def __init__(self, license_plate: str, vehicle_type: VehicleType):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    @abstractmethod
    def can_fit_in(self, spot_type: SpotType) -> bool:
        pass

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

    def can_fit_in(self, spot_type: SpotType) -> bool:
        return True  # Motorcycles fit anywhere

class Car(Vehicle):
    def __init__(self, license_plate: str):
        super().__init__(license_plate, VehicleType.CAR)

    def can_fit_in(self, spot_type: SpotType) -> bool:
        return spot_type in (SpotType.COMPACT, SpotType.REGULAR, SpotType.LARGE)

class Truck(Vehicle):
    def __init__(self, license_plate: str):
        super().__init__(license_plate, VehicleType.TRUCK)

    def can_fit_in(self, spot_type: SpotType) -> bool:
        return spot_type == SpotType.LARGE

Parking Spot and Floor

class ParkingSpot:
    def __init__(self, spot_id: str, spot_type: SpotType, floor: int):
        self.spot_id = spot_id
        self.spot_type = spot_type
        self.floor = floor
        self.status = SpotStatus.AVAILABLE
        self.parked_vehicle: Vehicle = None

    def is_available(self) -> bool:
        return self.status == SpotStatus.AVAILABLE

    def park(self, vehicle: Vehicle):
        if not vehicle.can_fit_in(self.spot_type):
            raise ValueError(f"Vehicle type {vehicle.vehicle_type} cannot fit in {self.spot_type} spot")
        self.parked_vehicle = vehicle
        self.status = SpotStatus.OCCUPIED

    def free(self):
        self.parked_vehicle = None
        self.status = SpotStatus.AVAILABLE

    def get_hourly_rate(self) -> float:
        rates = {SpotType.COMPACT: 2.0, SpotType.REGULAR: 3.0, SpotType.LARGE: 5.0}
        return rates[self.spot_type]

class ParkingFloor:
    def __init__(self, floor_number: int):
        self.floor_number = floor_number
        self.spots: dict[str, ParkingSpot] = {}
        # Available spots by type for O(1) lookup
        self._available: dict[SpotType, set] = {
            SpotType.COMPACT: set(),
            SpotType.REGULAR: set(),
            SpotType.LARGE: set(),
        }

    def add_spot(self, spot: ParkingSpot):
        self.spots[spot.spot_id] = spot
        if spot.is_available():
            self._available[spot.spot_type].add(spot.spot_id)

    def get_available_spot(self, vehicle: Vehicle) -> ParkingSpot:
        """Find first available spot that fits this vehicle."""
        # Check spots in order of size preference (smallest that fits)
        preference = {
            VehicleType.MOTORCYCLE: [SpotType.COMPACT, SpotType.REGULAR, SpotType.LARGE],
            VehicleType.CAR:        [SpotType.COMPACT, SpotType.REGULAR, SpotType.LARGE],
            VehicleType.TRUCK:      [SpotType.LARGE],
        }
        for spot_type in preference[vehicle.vehicle_type]:
            if self._available[spot_type]:
                spot_id = next(iter(self._available[spot_type]))
                return self.spots[spot_id]
        return None

    def park_vehicle(self, vehicle: Vehicle) -> ParkingSpot:
        spot = self.get_available_spot(vehicle)
        if not spot:
            return None
        spot.park(vehicle)
        self._available[spot.spot_type].discard(spot.spot_id)
        return spot

    def free_spot(self, spot_id: str):
        spot = self.spots[spot_id]
        spot.free()
        self._available[spot.spot_type].add(spot_id)

    def get_available_count(self) -> dict:
        return {stype: len(ids) for stype, ids in self._available.items()}

Ticket and Payment

import uuid
from dataclasses import dataclass, field

@dataclass
class ParkingTicket:
    ticket_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8].upper())
    vehicle: Vehicle = None
    spot: ParkingSpot = None
    entry_time: datetime = field(default_factory=datetime.now)
    exit_time: datetime = None
    amount_paid: float = 0.0

    def calculate_fee(self) -> float:
        if not self.exit_time:
            self.exit_time = datetime.now()
        duration_hours = (self.exit_time - self.entry_time).total_seconds() / 3600
        # Minimum 1 hour charge
        billable_hours = max(1.0, duration_hours)
        return round(billable_hours * self.spot.get_hourly_rate(), 2)

class PaymentProcessor:
    def process_payment(self, ticket: ParkingTicket, amount: float) -> bool:
        # In real system: integrate with payment gateway
        ticket.amount_paid = amount
        return True

Parking Lot (Main Controller)

import threading

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

    def __new__(cls):
        """Singleton pattern — one parking lot system."""
        if not cls._instance:
            with cls._lock:
                if not cls._instance:
                    cls._instance = super().__new__(cls)
                    cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self.name = "Main Parking Lot"
        self.floors: dict[int, ParkingFloor] = {}
        self.active_tickets: dict[str, ParkingTicket] = {}  # license_plate -> ticket
        self.payment_processor = PaymentProcessor()
        self._lock = threading.Lock()
        self._initialized = True

    def add_floor(self, floor: ParkingFloor):
        self.floors[floor.floor_number] = floor

    def park_vehicle(self, vehicle: Vehicle) -> ParkingTicket:
        with self._lock:
            if vehicle.license_plate in self.active_tickets:
                raise ValueError(f"Vehicle {vehicle.license_plate} already parked")

            # Find spot across all floors (prefer lower floors)
            for floor_num in sorted(self.floors.keys()):
                floor = self.floors[floor_num]
                spot = floor.park_vehicle(vehicle)
                if spot:
                    ticket = ParkingTicket(vehicle=vehicle, spot=spot)
                    self.active_tickets[vehicle.license_plate] = ticket
                    return ticket

            raise ValueError("Parking lot is full")

    def exit_vehicle(self, license_plate: str) -> ParkingTicket:
        with self._lock:
            if license_plate not in self.active_tickets:
                raise ValueError(f"No active ticket for {license_plate}")

            ticket = self.active_tickets.pop(license_plate)
            ticket.exit_time = datetime.now()
            fee = ticket.calculate_fee()
            self.payment_processor.process_payment(ticket, fee)

            # Free the spot
            floor = self.floors[ticket.spot.floor]
            floor.free_spot(ticket.spot.spot_id)

            return ticket

    def get_availability(self) -> dict:
        result = {}
        for floor_num, floor in self.floors.items():
            result[f"Floor {floor_num}"] = floor.get_available_count()
        return result

Key Design Decisions

  • Singleton ParkingLot: One system-wide controller; thread-safe double-checked locking
  • Available spot sets per type: O(1) availability check and spot retrieval instead of O(n) scan
  • Strategy for vehicle-spot compatibility: can_fit_in() on Vehicle, not on Spot — follows Tell Don’t Ask
  • Thread safety: Lock on park/exit operations to prevent double-parking races
  • Preference ordering: Cars prefer compact > regular > large to preserve large spots for trucks

Extension Points

  • Electric vehicle charging: Add SpotType.EV_CHARGING; EVVehicle prefers EV spots
  • Reservations: Reserve spots ahead of time; hold with timeout using scheduled job
  • Dynamic pricing: Peak hours command higher rates; inject pricing strategy into PaymentProcessor
  • Display boards: Observer pattern — floors publish availability changes; display boards subscribe
  • License plate recognition: Replace manual entry with camera API; auto-create Vehicle objects

{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”How do you design a Parking Lot system in an OOP interview?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Start with an abstract Vehicle hierarchy (Motorcycle, Car, Truck) where each implements can_fit_in(SpotType). A ParkingFloor manages spots using per-type sets for O(1) availability lookup. The ParkingLot singleton coordinates floors, issues ParkingTickets on entry, frees spots on exit, and uses a thread lock to prevent race conditions when multiple vehicles enter simultaneously.”}},{“@type”:”Question”,”name”:”How do you handle different vehicle types and spot sizes in a Parking Lot design?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Define SpotType enum (Compact, Regular, Large) and VehicleType enum. Each Vehicle subclass implements can_fit_in() — motorcycles fit anywhere, cars fit compact/regular/large, trucks only large. When finding a spot, prefer the smallest available spot that fits the vehicle to preserve larger spots for bigger vehicles. This avoids wasting large spots on motorcycles.”}},{“@type”:”Question”,”name”:”How do you calculate parking fees in a Parking Lot system?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”A ParkingTicket records entry_time at creation and exit_time when the vehicle leaves. Fee = max(1 hour minimum, actual duration in hours) × hourly rate for that spot type. Different spot types command different rates: compact is cheapest, large spots are most expensive. The PaymentProcessor abstracts the actual payment method (credit card, app, cash).”}},{“@type”:”Question”,”name”:”Why use the Singleton pattern for the Parking Lot controller?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”The parking lot represents a single physical system with shared state — spot availability, active tickets. A Singleton ensures all requests go through one controller, preventing inconsistencies like the same spot being assigned to two vehicles. Thread safety requires a lock around park/exit operations. Double-checked locking in __new__ ensures thread-safe lazy initialization without locking on every access.”}}]}

🏢 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: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering

🏢 Asked at: Apple Interview Guide 2026: iOS Systems, Hardware-Software Integration, and iCloud Architecture

🏢 Asked at: Snap Interview Guide

🏢 Asked at: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

🏢 Asked at: DoorDash Interview Guide

Asked at: Shopify Interview Guide

Scroll to Top