Case Study: Parking Lot System
Let's design a parking lot system - a classic LLD interview question. We'll apply everything we've learned: SOLID principles and design patterns.
In LLD interviews, always start by clarifying requirements, then identify entities, then define relationships. Don't jump straight to code.
---
Step 1: Gather Requirements
Before designing, we need to understand what the system should do:
- Park different vehicle types (car, motorcycle, truck)
- Different spot sizes for different vehicles
- Track which spots are available
- Calculate parking fee based on duration
- Issue tickets on entry
- Handle multiple entry/exit points
- Support different fee structures
- Easy to add new vehicle types
- Thread-safe for concurrent access
---
Step 2: Identify Core Entities
From the requirements, we can identify the main objects in our system:
---
Step 3: Design the Classes
Vehicle Hierarchy (Open/Closed Principle)
Instead of one class with a type field, we create a hierarchy. This makes it easy to add new vehicle types.
from abc import ABC, abstractmethod
from enum import Enum
class VehicleType(Enum):
MOTORCYCLE = 1
CAR = 2
TRUCK = 3
class Vehicle(ABC):
def __init__(self, license_plate: str):
self.license_plate = license_plate
@property
@abstractmethod
def vehicle_type(self) -> VehicleType:
pass
class Motorcycle(Vehicle):
@property
def vehicle_type(self) -> VehicleType:
return VehicleType.MOTORCYCLE
class Car(Vehicle):
@property
def vehicle_type(self) -> VehicleType:
return VehicleType.CAR
class Truck(Vehicle):
@property
def vehicle_type(self) -> VehicleType:
return VehicleType.TRUCK
---
Parking Spot Hierarchy
Different vehicles need different spot sizes:
class SpotType(Enum):
COMPACT = 1 # For motorcycles
REGULAR = 2 # For cars
LARGE = 3 # For trucks
class ParkingSpot(ABC):
def __init__(self, spot_id: str, floor: int):
self.spot_id = spot_id
self.floor = floor
self.vehicle: Vehicle = None
@property
@abstractmethod
def spot_type(self) -> SpotType:
pass
def is_available(self) -> bool:
return self.vehicle is None
def park(self, vehicle: Vehicle) -> bool:
if self.is_available() and self.can_fit(vehicle):
self.vehicle = vehicle
return True
return False
def remove_vehicle(self) -> Vehicle:
vehicle = self.vehicle
self.vehicle = None
return vehicle
@abstractmethod
def can_fit(self, vehicle: Vehicle) -> bool:
pass
class CompactSpot(ParkingSpot):
@property
def spot_type(self) -> SpotType:
return SpotType.COMPACT
def can_fit(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type == VehicleType.MOTORCYCLE
class RegularSpot(ParkingSpot):
@property
def spot_type(self) -> SpotType:
return SpotType.REGULAR
def can_fit(self, vehicle: Vehicle) -> bool:
return vehicle.vehicle_type in [VehicleType.MOTORCYCLE, VehicleType.CAR]
class LargeSpot(ParkingSpot):
@property
def spot_type(self) -> SpotType:
return SpotType.LARGE
def can_fit(self, vehicle: Vehicle) -> bool:
return True # Can fit any vehicle
---
Ticket Class
Tracks parking sessions:
from datetime import datetime
class Ticket:
def __init__(self, vehicle: Vehicle, spot: ParkingSpot):
self.ticket_id = self._generate_id()
self.vehicle = vehicle
self.spot = spot
self.entry_time = datetime.now()
self.exit_time: datetime = None
def _generate_id(self) -> str:
return f"TKT-{datetime.now().strftime('%Y%m%d%H%M%S')}"
def close(self):
self.exit_time = datetime.now()
def get_duration_hours(self) -> float:
if self.exit_time is None:
return 0
delta = self.exit_time - self.entry_time
return delta.total_seconds() / 3600
---
Fee Calculator (Strategy Pattern)
Different parking lots might have different pricing:
class FeeCalculator(ABC):
@abstractmethod
def calculate(self, ticket: Ticket) -> float:
pass
class HourlyFeeCalculator(FeeCalculator):
def __init__(self, hourly_rate: float):
self.hourly_rate = hourly_rate
def calculate(self, ticket: Ticket) -> float:
hours = ticket.get_duration_hours()
return max(1, round(hours)) * self.hourly_rate # Minimum 1 hour
class FlatRateFeeCalculator(FeeCalculator):
def __init__(self, daily_rate: float):
self.daily_rate = daily_rate
def calculate(self, ticket: Ticket) -> float:
hours = ticket.get_duration_hours()
days = max(1, int(hours / 24) + 1)
return days * self.daily_rate
---
Parking Lot - Putting It All Together
from typing import Dict, List, Optional
class ParkingLot:
def __init__(self, name: str, fee_calculator: FeeCalculator):
self.name = name
self.fee_calculator = fee_calculator
self.spots: Dict[str, ParkingSpot] = {}
self.active_tickets: Dict[str, Ticket] = {}
def add_spot(self, spot: ParkingSpot):
self.spots[spot.spot_id] = spot
def find_available_spot(self, vehicle: Vehicle) -> Optional[ParkingSpot]:
for spot in self.spots.values():
if spot.is_available() and spot.can_fit(vehicle):
return spot
return None
def park_vehicle(self, vehicle: Vehicle) -> Optional[Ticket]:
spot = self.find_available_spot(vehicle)
if spot is None:
return None # No spot available
spot.park(vehicle)
ticket = Ticket(vehicle, spot)
self.active_tickets[ticket.ticket_id] = ticket
return ticket
def exit_vehicle(self, ticket_id: str) -> float:
ticket = self.active_tickets.get(ticket_id)
if ticket is None:
raise ValueError("Invalid ticket")
ticket.close()
ticket.spot.remove_vehicle()
fee = self.fee_calculator.calculate(ticket)
del self.active_tickets[ticket_id]
return fee
def get_available_spots_count(self) -> Dict[SpotType, int]:
counts = {spot_type: 0 for spot_type in SpotType}
for spot in self.spots.values():
if spot.is_available():
counts[spot.spot_type] += 1
return counts
---
Step 4: See It In Action
# Create parking lot with hourly pricing
calculator = HourlyFeeCalculator(hourly_rate=10.0)
parking_lot = ParkingLot("Downtown Parking", calculator)
# Add spots
for i in range(5):
parking_lot.add_spot(CompactSpot(f"C{i}", floor=1))
for i in range(10):
parking_lot.add_spot(RegularSpot(f"R{i}", floor=1))
for i in range(3):
parking_lot.add_spot(LargeSpot(f"L{i}", floor=1))
# Park a car
my_car = Car("ABC-1234")
ticket = parking_lot.park_vehicle(my_car)
print(f"Parked! Ticket: {ticket.ticket_id}")
# Check availability
available = parking_lot.get_available_spots_count()
print(f"Available: {available}")
# Exit and pay
# (In real scenario, some time would pass)
fee = parking_lot.exit_vehicle(ticket.ticket_id)
print(f"Fee: ${fee}")
---
Class Diagram
---
SOLID Principles Applied
Each class has one job: Vehicle stores vehicle data, Ticket tracks time, FeeCalculator calculates fees.
Add new vehicle types or spot types without modifying existing code.
Any Vehicle subclass works wherever Vehicle is expected. Same for ParkingSpot.
FeeCalculator is a focused interface with just one method.
ParkingLot depends on FeeCalculator interface, not concrete implementations.
---
Extending the System
Just add ElectricVehicle(Vehicle) and ElectricSpot(ParkingSpot) with a charging method. Zero changes to existing code!
Key Takeaways
- Start with requirements - understand what you're building first
- Identify entities - find the nouns (Vehicle, Spot, Ticket)
- Use inheritance wisely - for genuine "is-a" relationships
- Depend on abstractions - use interfaces for flexibility
- Think about extension - how will new requirements fit in?
Congratulations! You've completed the LLD Fundamentals course. You now have the foundation to design clean, maintainable systems.