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_incompares 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.Lockguardspark_vehicleandexit_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