Difficulty : π’ Beginner | Time : 45 minutes | Patterns : Singleton, Strategy, Factory
π― Problem Statement
Design a parking lot system that can:
Handle multiple floors and different vehicle types
Track available and occupied spots
Calculate parking fees based on duration
Support multiple entry/exit points
Why This Problem? Parking Lot is the #1 most asked LLD problem because it tests OOP basics, relationships, and simple design patterns without being overwhelming.
π Step 1: Clarify Requirements
Interview Tip : ALWAYS ask clarifying questions before designing. Jumping straight to code is a red flag!
Questions to Ask the Interviewer
Category Question Possible Answer Vehicles What types of vehicles do we support? Cars, motorcycles, trucks Spots Are spot sizes different per vehicle type? Yes - compact, regular, large Floors How many floors? Multiple entry/exit? 3 floors, 2 entries, 2 exits Pricing Fixed rate or hourly? Different per vehicle? Hourly, varies by vehicle type Payment What payment methods? Cash, card, mobile Features Reservations? Electric charging? Out of scope for now
Functional Requirements
Park vehicles of different types (car, motorcycle, truck)
Assign the nearest available spot to a vehicle
Calculate fees based on vehicle type and duration
Support multiple payment methods
Display available spots per floor
Non-Functional Requirements
Handle concurrent entry/exit (thread safety)
Fast spot lookup - O(1) for availability check
Scalable to multiple parking lots
π§© Step 2: Identify Core Objects
Technique : List all nouns from requirements β potential classes. List all verbs β potential methods.
Vehicles Car, Motorcycle, Truck
Parking Spots Compact, Regular, Large
Infrastructure ParkingLot, Floor, Entry/Exit
Entity-Responsibility Mapping
Entity Responsibilities VehicleStore type, license plate; check if fits in spot ParkingSpotTrack availability; assign/remove vehicles FloorManage spots on one floor; find available spot ParkingLotCoordinate all floors; issue tickets ParkingTicketTrack entry time, spot; calculate fee PaymentMethodProcess payment (Strategy pattern)
π Step 3: Class Diagram
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ParkingLot (Singleton) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β - id: UUID β
β - name: String β
β - floors: List<Floor> β
β - activeTickets: Dict<String, ParkingTicket> β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β + parkVehicle(vehicle): ParkingTicket β
β + unparkVehicle(ticketId): float β
β + getAvailability(): List<FloorInfo> β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β 1..*
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Floor β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β - floorNumber: int β
β - spots: Dict[SpotType, List<ParkingSpot>] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β + findAvailableSpot(vehicle): ParkingSpot β
β + getAvailableCount(spotType): int β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β 1..*
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ParkingSpot β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β - id: String β
β - spotType: SpotType (COMPACT/REGULAR/LARGE) β
β - isAvailable: bool β
β - vehicle: Vehicle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β + canFit(vehicle): bool β
β + assignVehicle(vehicle): bool β
β + removeVehicle(): Vehicle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
β <<abstract>> β β ParkingTicket β
β Vehicle β βββββββββββββββββββββββββββββ€
βββββββββββββββββββββββββ€ β - id: String β
β - licensePlate: str β β - licensePlate: String β
β - vehicleType: enum β β - spotId: String β
βββββββββββββββββββββββββ€ β - entryTime: DateTime β
β + canFitIn(spot): boolβ β - exitTime: DateTime β
βββββββββββββββββββββββββ βββββββββββββββββββββββββββββ€
β³ β + calculateFee(): float β
ββββββΌβββββ βββββββββββββββββββββββββββββ
β β β
βββββ΄β βββ΄ββ ββ΄βββββ
βCar β βMotoβ βTruckβ
ββββββ βββββ βββββββ
π» Step 4: Implementation
Enums & Config
Payment (Strategy)
Enums and Constants from enum import Enum
from datetime import datetime
from typing import Optional, Dict, List
from dataclasses import dataclass
import uuid
class VehicleType ( Enum ):
MOTORCYCLE = 1
CAR = 2
TRUCK = 3
class SpotType ( Enum ):
COMPACT = 1 # Fits motorcycle, car
REGULAR = 2 # Fits motorcycle, car
LARGE = 3 # Fits all vehicles
class TicketStatus ( Enum ):
ACTIVE = 1
PAID = 2
LOST = 3
# Pricing per hour
HOURLY_RATES = {
VehicleType. MOTORCYCLE : 1.0 ,
VehicleType. CAR : 2.0 ,
VehicleType. TRUCK : 4.0
}
Vehicle Classes class Vehicle :
def __init__ ( self , license_plate : str , vehicle_type : VehicleType):
self .license_plate = license_plate
self .vehicle_type = vehicle_type
def can_fit_in ( self , spot_type : SpotType) -> bool :
"""Check if vehicle fits in spot type"""
raise NotImplementedError
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 # Motorcycle fits 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 # Only large spots
Parking Spot class ParkingSpot :
def __init__ ( self , spot_id : str , floor_number : int , spot_type : SpotType):
self .id = spot_id
self .floor_number = floor_number
self .spot_type = spot_type
self ._vehicle: Optional[Vehicle] = None
self ._is_available = True
@ property
def is_available ( self ) -> bool :
return self ._is_available
def can_fit ( self , vehicle : Vehicle) -> bool :
"""Check if vehicle can park here"""
return self ._is_available and vehicle.can_fit_in( self .spot_type)
def assign_vehicle ( self , vehicle : Vehicle) -> bool :
"""Park a vehicle in this spot"""
if not self .can_fit(vehicle):
return False
self ._vehicle = vehicle
self ._is_available = False
return True
def remove_vehicle ( self ) -> Optional[Vehicle]:
"""Remove vehicle from spot"""
vehicle = self ._vehicle
self ._vehicle = None
self ._is_available = True
return vehicle
Parking Ticket @dataclass
class ParkingTicket :
id : str
license_plate: str
spot_id: str
floor_number: int
entry_time: datetime
exit_time: Optional[datetime] = None
status: TicketStatus = TicketStatus. ACTIVE
@ staticmethod
def create ( vehicle : Vehicle, spot : ParkingSpot) -> 'ParkingTicket' :
return ParkingTicket(
id = str (uuid.uuid4()),
license_plate = vehicle.license_plate,
spot_id = spot.id,
floor_number = spot.floor_number,
entry_time = datetime.now()
)
def calculate_fee ( self , vehicle_type : VehicleType) -> float :
"""Calculate parking fee based on duration"""
if self .exit_time is None :
self .exit_time = datetime.now()
duration = self .exit_time - self .entry_time
hours = max ( 1 , int (duration.total_seconds() / 3600 ) + 1 ) # Round up
return hours * HOURLY_RATES [vehicle_type]
class Floor :
def __init__ ( self , floor_number : int ):
self .floor_number = floor_number
self .spots: Dict[SpotType, List[ParkingSpot]] = {
SpotType. COMPACT : [],
SpotType. REGULAR : [],
SpotType. LARGE : []
}
def add_spot ( self , spot : ParkingSpot):
"""Add a parking spot to this floor"""
self .spots[spot.spot_type].append(spot)
def get_available_count ( self , spot_type : SpotType) -> int :
"""Get count of available spots of given type"""
return sum ( 1 for spot in self .spots[spot_type] if spot.is_available)
def find_available_spot ( self , vehicle : Vehicle) -> Optional[ParkingSpot]:
"""Find first available spot that fits the vehicle"""
# Try to find the smallest suitable spot
for spot_type in SpotType:
for spot in self .spots[spot_type]:
if spot.can_fit(vehicle):
return spot
return None
def get_display_info ( self ) -> dict :
"""Get floor availability for display"""
return {
"floor" : self .floor_number,
"compact_available" : self .get_available_count(SpotType. COMPACT ),
"regular_available" : self .get_available_count(SpotType. REGULAR ),
"large_available" : self .get_available_count(SpotType. LARGE )
}
Parking Lot import threading
class ParkingLot :
_instance = None
_lock = threading.Lock()
def __new__ ( cls , * args , ** kwargs ):
if cls ._instance is None :
with cls ._lock:
if cls ._instance is None :
cls ._instance = super (). __new__ ( cls )
return cls ._instance
def __init__ ( self , name : str , num_floors : int ):
if hasattr ( self , '_initialized' ):
return
self .name = name
self .floors: List[Floor] = []
self .active_tickets: Dict[ str , ParkingTicket] = {} # ticket_id -> ticket
self .vehicle_to_spot: Dict[ str , ParkingSpot] = {} # license -> spot
self ._lock = threading.Lock()
self ._initialized = True
# Initialize floors
for i in range (num_floors):
self .floors.append(Floor(i + 1 ))
def add_spots ( self , floor_number : int , spot_type : SpotType, count : int ):
"""Add parking spots to a floor"""
floor = self .floors[floor_number - 1 ]
for i in range (count):
spot_id = f "F { floor_number } - { spot_type.name[ 0 ] }{ i + 1 } "
spot = ParkingSpot(spot_id, floor_number, spot_type)
floor.add_spot(spot)
def park_vehicle ( self , vehicle : Vehicle) -> Optional[ParkingTicket]:
"""Park a vehicle and return ticket"""
with self ._lock:
# Check if vehicle already parked
if vehicle.license_plate in self .vehicle_to_spot:
raise ValueError ( "Vehicle already parked" )
# Find available spot
spot = self ._find_available_spot(vehicle)
if spot is None :
return None # No spot available
# Assign vehicle to spot
spot.assign_vehicle(vehicle)
# Create ticket
ticket = ParkingTicket.create(vehicle, spot)
self .active_tickets[ticket.id] = ticket
self .vehicle_to_spot[vehicle.license_plate] = spot
return ticket
def unpark_vehicle ( self , ticket_id : str ) -> Optional[ float ]:
"""Unpark vehicle and return fee"""
with self ._lock:
ticket = self .active_tickets.get(ticket_id)
if ticket is None or ticket.status != TicketStatus. ACTIVE :
return None
# Get spot and vehicle
spot = self .vehicle_to_spot.get(ticket.license_plate)
if spot is None :
return None
vehicle = spot.remove_vehicle()
# Calculate fee
fee = ticket.calculate_fee(vehicle.vehicle_type)
ticket.status = TicketStatus. PAID
# Cleanup
del self .vehicle_to_spot[ticket.license_plate]
return fee
def _find_available_spot ( self , vehicle : Vehicle) -> Optional[ParkingSpot]:
"""Find nearest available spot for vehicle"""
for floor in self .floors:
spot = floor.find_available_spot(vehicle)
if spot:
return spot
return None
def get_availability ( self ) -> List[ dict ]:
"""Get availability info for all floors"""
return [floor.get_display_info() for floor in self .floors]
Payment System class PaymentMethod ( ABC ):
@abstractmethod
def process ( self , amount : float ) -> bool :
pass
class CashPayment ( PaymentMethod ):
def process ( self , amount : float ) -> bool :
print ( f "Processing cash payment: $ { amount :.2f} " )
return True
class CardPayment ( PaymentMethod ):
def __init__ ( self , card_number : str ):
self .card_number = card_number
def process ( self , amount : float ) -> bool :
print ( f "Processing card payment: $ { amount :.2f} " )
return True
class PaymentProcessor :
def process_payment ( self , ticket_id : str ,
parking_lot : ParkingLot,
payment_method : PaymentMethod) -> bool :
fee = parking_lot.unpark_vehicle(ticket_id)
if fee is None :
return False
return payment_method.process(fee)
βΆοΈ Step 5: Usage Example
# Create parking lot
parking_lot = ParkingLot( "Downtown Parking" , num_floors = 3 )
# Add spots to each floor
for floor in range ( 1 , 4 ):
parking_lot.add_spots(floor, SpotType. COMPACT , 10 )
parking_lot.add_spots(floor, SpotType. REGULAR , 20 )
parking_lot.add_spots(floor, SpotType. LARGE , 5 )
# Park vehicles
car = Car( "ABC-123" )
motorcycle = Motorcycle( "XYZ-789" )
truck = Truck( "TRK-001" )
ticket1 = parking_lot.park_vehicle(car)
print ( f "Car parked at: { ticket1.spot_id } " )
ticket2 = parking_lot.park_vehicle(motorcycle)
print ( f "Motorcycle parked at: { ticket2.spot_id } " )
# Check availability
print (parking_lot.get_availability())
# Unpark and pay
import time
time.sleep( 2 ) # Simulate parking duration
processor = PaymentProcessor()
processor.process_payment(ticket1.id, parking_lot, CashPayment())
π― Key Design Decisions
Why Singleton for ParkingLot?
In most real-world scenarios, thereβs only one parking lot instance per location. Singleton ensures:
Consistent state across all entry/exit panels
No duplicate instances wasting memory
Single point of control for all operations
Trade-off : Makes unit testing harder. Consider dependency injection for testability.
Why Strategy Pattern for Payment?
Allows adding new payment methods (Apple Pay, UPI, Crypto) without modifying existing code:
Each payment method encapsulates its own logic
Easy to A/B test payment methods
Follows Open/Closed Principle
Alternative : Could use Factory pattern if payment logic is simpler.
Multiple entry/exit panels can operate simultaneously:
Prevents race conditions when assigning spots
Ensures ticket consistency
Handles concurrent unpark operations
Trade-off : Slight performance hit. Could use read-write locks for better read performance.
Using inheritance for vehicles provides:
Polymorphic behavior for canFitIn() method
Easy addition of new vehicle types
Type-safe spot assignment
Alternative : Could use composition with VehicleType enum for simpler cases.
π Interview Extensions
Interviewer Favorite : βHow would you extend this design to supportβ¦β
class ReservedSpot ( ParkingSpot ):
def __init__ ( self , spot_id , floor_number , spot_type , reserved_for : str ):
super (). __init__ (spot_id, floor_number, spot_type)
self .reserved_for = reserved_for # License plate
def can_fit ( self , vehicle : Vehicle) -> bool :
return ( super ().can_fit(vehicle) and
vehicle.license_plate == self .reserved_for)
Electric Vehicle Charging
class EVChargingSpot ( ParkingSpot ):
def __init__ ( self , spot_id , floor_number , charger_type : str ):
super (). __init__ (spot_id, floor_number, SpotType. REGULAR )
self .charger_type = charger_type # "Level2", "DC Fast"
self .charging_rate_per_kwh = 0.15
def calculate_charging_fee ( self , kwh_used : float ) -> float :
return kwh_used * self .charging_rate_per_kwh
class MonthlyPass :
def __init__ ( self , holder_license : str , spot_type : SpotType):
self .holder_license = holder_license
self .spot_type = spot_type
self .valid_until = datetime.now() + timedelta( days = 30 )
def is_valid ( self ) -> bool :
return datetime.now() < self .valid_until
# Modify fee calculation
def calculate_fee ( self , vehicle_type : VehicleType, has_pass : bool ) -> float :
if has_pass:
return 0.0
# ... regular calculation
def handle_lost_ticket ( self , license_plate : str ) -> float :
"""Charge maximum daily rate for lost tickets"""
spot = self .vehicle_to_spot.get(license_plate)
if not spot:
raise ValueError ( "Vehicle not found in parking lot" )
vehicle = spot._vehicle
max_hours = 24
return max_hours * HOURLY_RATES [vehicle.vehicle_type]
# Add validation layers
class ParkingValidator :
def validate_entry ( self , vehicle : Vehicle) -> bool :
# Check if vehicle is banned
# Check if license plate is valid
# Check if lot has capacity
pass
def validate_exit ( self , ticket : ParkingTicket) -> bool :
# Verify ticket authenticity
# Check for payment status
pass
π Complexity Analysis
Operation Time Complexity Space Complexity parkVehicle()O(F Γ S) O(1) unparkVehicle()O(1) O(1) getAvailability()O(F) O(F) findAvailableSpot()O(F Γ S) O(1)
Where F = number of floors, S = spots per floor
Optimization : Use a heap/priority queue per floor for O(log S) spot finding!
β
What Makes This Design Good?
Principle How Itβs Applied Single Responsibility Each class has one job: Vehicle stores data, Spot manages allocation, Floor coordinates Open/Closed Add new payment methods without changing existing code Liskov Substitution Car, Motorcycle, Truck all substitute for Vehicle Interface Segregation PaymentMethod is focused, not a fat interface Dependency Inversion ParkingLot depends on abstract Vehicle, not concrete types