🔥 0
0
Lesson 4 of 4 25 min +150 XP

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.

Interview Tip:

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:

Functional Requirements
  • 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
Non-Functional Requirements
  • 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:

ParkingLot ParkingSpot Vehicle Ticket Compact Large Car Moto Truck

---

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
SOLID Applied: Open/Closed - add ElectricCar without changing existing code.

---

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
Pattern Applied: Strategy - swap fee calculation without changing ParkingLot.

---

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

ParkingLot park() exit() find() FeeCalculator calculate() ParkingSpot park() remove() Ticket entry exit duration HourlyFee FlatRateFee Compact Regular Large Vehicle Main class Interface Implementation

---

SOLID Principles Applied

S
Single Responsibility

Each class has one job: Vehicle stores vehicle data, Ticket tracks time, FeeCalculator calculates fees.

O
Open/Closed

Add new vehicle types or spot types without modifying existing code.

L
Liskov Substitution

Any Vehicle subclass works wherever Vehicle is expected. Same for ParkingSpot.

I
Interface Segregation

FeeCalculator is a focused interface with just one method.

D
Dependency Inversion

ParkingLot depends on FeeCalculator interface, not concrete implementations.

---

Extending the System

Adding Electric Vehicles:

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.

🧠 Quick Quiz

Test your understanding of this lesson.

1

Why do we create separate classes for Car, Motorcycle, and Truck instead of one Vehicle class with a 'type' field?

2

What design pattern does ParkingSpotFactory use?

3

Why does ParkingLot use a FeeCalculator interface instead of calculating fees directly?

4

What happens when we need to add Electric Vehicle support?

Design Patterns