Low-Level Design Interview: Design a Parking Lot System

Low-Level Design: Design a Parking Lot System

Parking lot is a classic Low-Level Design (LLD) / Object-Oriented Design interview question at Uber, Lyft, Amazon, and Microsoft. It tests your ability to model a real-world system with classes, inheritance, and design patterns.

Requirements Clarification

Functional Requirements

  • Multiple floors, each with multiple spots of different sizes (compact, regular, large, motorcycle)
  • Park a vehicle and get a ticket
  • Unpark a vehicle, calculate fee, and process payment
  • Display available spots per floor per type
  • Support different vehicle types (motorcycle, car, bus/truck)

Non-Functional

  • Thread-safe: multiple entrance/exit lanes operating concurrently
  • Extensible: easy to add new vehicle types or pricing strategies

Class Design

from enum import Enum
from datetime import datetime
import threading

class VehicleType(Enum):
    MOTORCYCLE = 1
    CAR = 2
    BUS = 3

class SpotSize(Enum):
    SMALL = 1   # motorcycles only
    MEDIUM = 2  # motorcycles and cars
    LARGE = 3   # all vehicles

class Vehicle:
    def __init__(self, plate: str, vehicle_type: VehicleType):
        self.plate = plate
        self.vehicle_type = vehicle_type
        self.spot = None  # assigned spot

    def can_fit_in(self, spot_size: SpotSize) -> bool:
        if self.vehicle_type == VehicleType.MOTORCYCLE:
            return True  # fits in any spot
        if self.vehicle_type == VehicleType.CAR:
            return spot_size in (SpotSize.MEDIUM, SpotSize.LARGE)
        if self.vehicle_type == VehicleType.BUS:
            return spot_size == SpotSize.LARGE
        return False
class ParkingSpot:
    def __init__(self, spot_id: str, floor: int, size: SpotSize):
        self.spot_id = spot_id
        self.floor = floor
        self.size = size
        self.is_occupied = False
        self.vehicle = None
        self._lock = threading.Lock()

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

    def unpark(self) -> Vehicle:
        with self._lock:
            vehicle = self.vehicle
            self.is_occupied = False
            self.vehicle = None
            if vehicle:
                vehicle.spot = None
            return vehicle
class Ticket:
    def __init__(self, ticket_id: str, vehicle: Vehicle, spot: ParkingSpot):
        self.ticket_id = ticket_id
        self.vehicle = vehicle
        self.spot = spot
        self.entry_time = datetime.now()
        self.exit_time = None
        self.fee = 0.0

Pricing Strategy (Strategy Pattern)

from abc import ABC, abstractmethod

class PricingStrategy(ABC):
    @abstractmethod
    def calculate_fee(self, entry_time: datetime, exit_time: datetime,
                       vehicle_type: VehicleType) -> float:
        pass

class HourlyPricing(PricingStrategy):
    RATES = {
        VehicleType.MOTORCYCLE: 1.0,
        VehicleType.CAR: 2.0,
        VehicleType.BUS: 5.0,
    }

    def calculate_fee(self, entry_time, exit_time, vehicle_type):
        duration = (exit_time - entry_time).total_seconds() / 3600
        hours = max(1, int(duration) + (1 if duration % 1 else 0))  # ceil
        return hours * self.RATES[vehicle_type]

class FlatRatePricing(PricingStrategy):
    def calculate_fee(self, entry_time, exit_time, vehicle_type):
        return 10.0  # flat rate regardless of time

ParkingFloor and ParkingLot

class ParkingFloor:
    def __init__(self, floor_number: int, spots: list):
        self.floor_number = floor_number
        self.spots = spots  # list of ParkingSpot
        self._available = {size: [] for size in SpotSize}
        for spot in spots:
            self._available[spot.size].append(spot)

    def find_spot(self, vehicle: Vehicle):
        # Find smallest fitting spot (motorcycle prefers small spots)
        preferred_size = {
            VehicleType.MOTORCYCLE: SpotSize.SMALL,
            VehicleType.CAR: SpotSize.MEDIUM,
            VehicleType.BUS: SpotSize.LARGE,
        }[vehicle.vehicle_type]

        for size in [preferred_size] + list(SpotSize):
            for spot in self._available.get(size, []):
                if not spot.is_occupied and vehicle.can_fit_in(size):
                    return spot
        return None

    def available_count(self, size: SpotSize) -> int:
        return sum(1 for s in self._available.get(size, []) if not s.is_occupied)
import uuid

class ParkingLot:
    def __init__(self, floors: list, pricing: PricingStrategy):
        self.floors = floors
        self.pricing = pricing
        self.tickets = {}  # ticket_id -> Ticket
        self._lock = threading.Lock()

    def park(self, vehicle: Vehicle):
        for floor in self.floors:
            spot = floor.find_spot(vehicle)
            if spot and spot.park(vehicle):
                ticket_id = str(uuid.uuid4())[:8]
                ticket = Ticket(ticket_id, vehicle, spot)
                with self._lock:
                    self.tickets[ticket_id] = ticket
                print(f"Parked {vehicle.plate} at floor {spot.floor} spot {spot.spot_id}")
                return ticket
        raise Exception("Parking lot is full")

    def unpark(self, ticket_id: str) -> float:
        with self._lock:
            ticket = self.tickets.pop(ticket_id, None)
        if not ticket:
            raise Exception("Invalid ticket")
        ticket.exit_time = datetime.now()
        ticket.spot.unpark()
        ticket.fee = self.pricing.calculate_fee(
            ticket.entry_time, ticket.exit_time, ticket.vehicle.vehicle_type)
        print("Fee for %s: $%.2f" % (ticket.vehicle.plate, ticket.fee))
        return ticket.fee

    def display_availability(self):
        for floor in self.floors:
            print(f"Floor {floor.floor_number}:")
            for size in SpotSize:
                print(f"  {size.name}: {floor.available_count(size)} available")

Key Design Decisions

  • Strategy pattern for pricing: easy to swap HourlyPricing for FlatRatePricing or add DynamicPricing
  • Thread safety: per-spot lock for parking operations, global lock for ticket registry
  • Best-fit allocation: motorcycles prefer small spots, leaving medium spots for cars
  • Extensibility: new vehicle types just implement can_fit_in; new pricing just implements calculate_fee

Interview Tips

  • Start by asking clarifying questions about vehicle types and pricing models
  • Draw the class diagram before coding: Vehicle, ParkingSpot, ParkingFloor, ParkingLot, Ticket, PricingStrategy
  • Mention thread safety – multiple entry/exit lanes need concurrent access
  • Name the design patterns you use (Strategy for pricing, Lock for thread safety)
  • Discuss the best-fit vs first-fit spot allocation trade-off

Scroll to Top