Parking Lot System
Introduction
Section titled “Introduction”In this comprehensive case study, we’ll design a Parking Lot System from scratch using the systematic 8-step approach. This is one of the most popular LLD interview problems, and mastering it will give you a solid foundation for tackling similar resource management problems.
Problem Statement
Section titled “Problem Statement”Design a Parking Lot System that can:
- Support multiple levels/floors, each with a certain number of parking spots
- Support different types of vehicles (cars, motorcycles, trucks)
- Assign parking spots to vehicles upon entry
- Release spots when vehicles exit
- Track real-time availability of parking spots
- Handle multiple entry and exit points with concurrent access
- Calculate fees based on parking duration
Step 1: Clarify Requirements
Section titled “Step 1: Clarify Requirements”Before designing, let’s ask the right questions!
Clarifying Questions
Section titled “Clarifying Questions”Functional Requirements:
- Vehicle Types: What types of vehicles? → Cars, Motorcycles, Trucks
- Spot Types: Do spots have different sizes? → Yes, spots are sized (Small, Medium, Large)
- Spot Assignment: How should spots be assigned? → Based on vehicle size compatibility
- Multiple Levels: How many floors? → Configurable, multiple floors supported
- Entry/Exit: Multiple gates? → Yes, multiple entry and exit points
- Billing: How is billing calculated? → Time-based (per hour)
Non-Functional Requirements:
- Concurrency: Multiple vehicles can enter/exit simultaneously → Yes, thread safety needed
- Scalability: How many spots? → Configurable, should handle hundreds
- Availability: Real-time spot availability → Yes, must be accurate
Edge Cases to Consider:
- What if parking lot is full?
- What if invalid vehicle type?
- What if vehicle tries to park in wrong-sized spot?
- What if ticket is invalid or already used?
- What if multiple vehicles try to park simultaneously?
Requirements Summary
Section titled “Requirements Summary”Step 2: Identify Actors
Section titled “Step 2: Identify Actors”Actors are external entities that interact with the system.
Who Uses This System?
Section titled “Who Uses This System?”Primary Actors:
- Driver/Customer - Enters parking lot, parks vehicle, exits and pays
- Parking Attendant - (Future) Manually handles special cases
- System Administrator - (Future) Configures pricing, manages spots
Secondary Actors:
- Payment System - (Future) External payment gateway
- Notification System - (Future) Sends alerts/notifications
Actor Interactions
Section titled “Actor Interactions”Step 3: Identify Entities
Section titled “Step 3: Identify Entities”Entities are core objects in your system that have data and behavior.
The Noun Hunt
Section titled “The Noun Hunt”Looking at the requirements, we identify these core entities:
- ParkingLotSystem - Main orchestrator
- ParkingFloor - Represents a level/floor
- ParkingSpot - Individual parking space
- Vehicle - Abstract representation of vehicles
- ParkingTicket - Links vehicle to spot, tracks time
- VehicleSize - Enum for size types
Entity Relationships
Section titled “Entity Relationships”Each class should have a single, well-defined responsibility (SRP).
Responsibility Assignment
Section titled “Responsibility Assignment”ParkingLotSystem:
- Manage parking floors
- Coordinate vehicle parking/unparking
- Track active tickets
- Delegate fee calculation to strategy
- Delegate spot finding to strategy
ParkingFloor:
- Manage spots on a specific level
- Provide spot lookup by ID
- Return list of spots
ParkingSpot:
- Track its own occupancy state
- Validate if vehicle can fit
- Park/unpark vehicle
ParkingTicket:
- Store entry/exit timestamps
- Link vehicle to spot
- Calculate parking duration
Vehicle:
- Store vehicle information (license, size)
- Provide vehicle type information
Responsibility Visualization
Section titled “Responsibility Visualization”Step 5: Design Class Diagrams
Section titled “Step 5: Design Class Diagrams”Class diagrams show structure, relationships, and design patterns.
classDiagram
class ParkingLotSystem {
-ParkingLotSystem instance
-List~ParkingFloor~ floors
-Map~String,ParkingTicket~ activeTickets
-FeeStrategy feeStrategy
-ParkingStrategy parkingStrategy
+getInstance() ParkingLotSystem
+addFloor(ParkingFloor) void
+parkVehicle(Vehicle) Optional~ParkingTicket~
+unparkVehicle(String) Optional~Double~
+setFeeStrategy(FeeStrategy) void
+setParkingStrategy(ParkingStrategy) void
}
class ParkingFloor {
-int floorNumber
-Map~String,ParkingSpot~ spots
+addSpot(ParkingSpot) void
+getSpots() List~ParkingSpot~
}
class ParkingSpot {
-String spotId
-VehicleSize spotSize
-boolean isOccupied
-Vehicle parkedVehicle
+canFitVehicle(Vehicle) boolean
+parkVehicle(Vehicle) void
+unparkVehicle() void
+isAvailable() boolean
}
class ParkingTicket {
-String ticketId
-Vehicle vehicle
-ParkingSpot spot
-long entryTimestamp
-long exitTimestamp
+getDurationInHours() long
+setExitTimestamp(long) void
}
class Vehicle {
<<abstract>>
-String licenseNumber
-VehicleSize size
+getSize() VehicleSize
+getLicenseNumber() String
}
class Car {
+Car(String)
}
class Truck {
+Truck(String)
}
class Bike {
+Bike(String)
}
class VehicleSize {
<<enumeration>>
SMALL
MEDIUM
LARGE
}
class FeeStrategy {
<<interface>>
+calculateFee(ParkingTicket) double
}
class ParkingStrategy {
<<interface>>
+findSpot(List~ParkingFloor~, Vehicle) Optional~ParkingSpot~
}
class FlatRateFeeStrategy {
-double RATE_PER_HOUR
+calculateFee(ParkingTicket) double
}
class NearestFirstStrategy {
+findSpot(List~ParkingFloor~, Vehicle) Optional~ParkingSpot~
}
class VehicleFactory {
+createVehicle(VehicleSize, String) Vehicle
}
ParkingLotSystem "1" *-- "many" ParkingFloor : contains
ParkingLotSystem "1" *-- "many" ParkingTicket : manages
ParkingLotSystem --> FeeStrategy : uses
ParkingLotSystem --> ParkingStrategy : uses
ParkingFloor "1" *-- "many" ParkingSpot : contains
ParkingSpot --> Vehicle : references
ParkingTicket --> Vehicle : references
ParkingTicket --> ParkingSpot : references
Vehicle <|-- Car
Vehicle <|-- Truck
Vehicle <|-- Bike
Vehicle --> VehicleSize : has
ParkingSpot --> VehicleSize : has
FeeStrategy <|.. FlatRateFeeStrategy
ParkingStrategy <|.. NearestFirstStrategy
VehicleFactory ..> Vehicle : creates
1. Singleton Pattern - ParkingLotSystem
- Ensures single instance across multiple gates
- Prevents data inconsistency
2. Strategy Pattern - FeeStrategy & ParkingStrategy
- Encapsulates algorithms (fee calculation, spot finding)
- Allows runtime swapping of strategies
3. Factory Pattern - VehicleFactory
- Centralizes vehicle creation
- Decouples client from concrete vehicle classes
4. Inheritance - Vehicle hierarchy
- Code reuse for common vehicle attributes
- Polymorphism for different vehicle types
Step 6: Define Contracts & APIs
Section titled “Step 6: Define Contracts & APIs”Contracts define how classes interact - method signatures, interfaces, and behavior.
FeeStrategy Interface:
1interface FeeStrategy:2 """3 Strategy for calculating parking fees.4 Different implementations can use different pricing models.5 """6 def calculate_fee(ticket: ParkingTicket) -> float:7 """8 Calculate parking fee based on ticket information.9
10 Args:11 ticket: Parking ticket with entry/exit timestamps12
13 Returns:14 float: Calculated fee amount15
16 Raises:17 InvalidTicketException: If ticket is invalid18 """19 passParkingStrategy Interface:
1interface ParkingStrategy:2 """3 Strategy for finding parking spots.4 Different implementations can use different selection algorithms.5 """6 def find_spot(floors: List[ParkingFloor], vehicle: Vehicle) -> Optional[ParkingSpot]:7 """8 Find an available parking spot for the vehicle.9
10 Args:11 floors: List of parking floors to search12 vehicle: Vehicle to park13
14 Returns:15 Optional[ParkingSpot]: Available spot if found, None otherwise16 """17 passFeeStrategy Interface:
1interface FeeStrategy {2 /**3 * Calculate parking fee based on ticket information.4 * @param ticket Parking ticket with entry/exit timestamps5 * @return Calculated fee amount6 * @throws InvalidTicketException If ticket is invalid7 */8 double calculateFee(ParkingTicket ticket);9}ParkingStrategy Interface:
1interface ParkingStrategy {2 /**3 * Find an available parking spot for the vehicle.4 * @param floors List of parking floors to search5 * @param vehicle Vehicle to park6 * @return Available spot if found, empty Optional otherwise7 */8 Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle);9}Core Class Contracts
Section titled “Core Class Contracts”ParkingLotSystem:
1class ParkingLotSystem:2 def get_instance() -> ParkingLotSystem:3 """4 Get singleton instance of parking lot system.5
6 Returns:7 ParkingLotSystem: The single instance8 """9 pass10
11 def park_vehicle(vehicle: Vehicle) -> Optional[ParkingTicket]:12 """13 Park a vehicle and return a ticket.14
15 Args:16 vehicle: Vehicle to park17
18 Returns:19 Optional[ParkingTicket]: Ticket if parking successful, None if no spots available20
21 Raises:22 InvalidVehicleException: If vehicle is invalid23 """24 pass25
26 def unpark_vehicle(ticket_id: str) -> Optional[float]:27 """28 Unpark a vehicle and calculate fee.29
30 Args:31 ticket_id: Ticket ID from parking32
33 Returns:34 Optional[float]: Fee amount if successful, None if ticket invalid35
36 Raises:37 InvalidTicketException: If ticket doesn't exist or already used38 """39 pass40
41 def set_fee_strategy(strategy: FeeStrategy) -> None:42 """Set the fee calculation strategy."""43 pass44
45 def set_parking_strategy(strategy: ParkingStrategy) -> None:46 """Set the parking spot selection strategy."""47 passParkingSpot:
1class ParkingSpot:2 def can_fit_vehicle(vehicle: Vehicle) -> bool:3 """4 Check if vehicle can fit in this spot.5
6 Args:7 vehicle: Vehicle to check8
9 Returns:10 bool: True if vehicle fits and spot is available11 """12 pass13
14 def park_vehicle(vehicle: Vehicle) -> None:15 """16 Park vehicle in this spot.17
18 Args:19 vehicle: Vehicle to park20
21 Raises:22 SpotOccupiedException: If spot is already occupied23 VehicleSizeMismatchException: If vehicle doesn't fit24 """25 pass26
27 def unpark_vehicle() -> None:28 """29 Remove vehicle from spot.30
31 Raises:32 SpotEmptyException: If spot is not occupied33 """34 passParkingLotSystem:
1class ParkingLotSystem {2 /**3 * Get singleton instance of parking lot system.4 * @return The single instance5 */6 public static ParkingLotSystem getInstance();7
8 /**9 * Park a vehicle and return a ticket.10 * @param vehicle Vehicle to park11 * @return Ticket if parking successful, empty Optional if no spots available12 * @throws InvalidVehicleException If vehicle is invalid13 */14 public Optional<ParkingTicket> parkVehicle(Vehicle vehicle);15
16 /**17 * Unpark a vehicle and calculate fee.18 * @param ticketId Ticket ID from parking19 * @return Fee amount if successful, empty Optional if ticket invalid20 * @throws InvalidTicketException If ticket doesn't exist or already used21 */22 public Optional<Double> unparkVehicle(String ticketId);23
24 /** Set the fee calculation strategy. */25 public void setFeeStrategy(FeeStrategy strategy);26
27 /** Set the parking spot selection strategy. */28 public void setParkingStrategy(ParkingStrategy strategy);29}ParkingSpot:
1class ParkingSpot {2 /**3 * Check if vehicle can fit in this spot.4 * @param vehicle Vehicle to check5 * @return True if vehicle fits and spot is available6 */7 public boolean canFitVehicle(Vehicle vehicle);8
9 /**10 * Park vehicle in this spot.11 * @param vehicle Vehicle to park12 * @throws SpotOccupiedException If spot is already occupied13 * @throws VehicleSizeMismatchException If vehicle doesn't fit14 */15 public void parkVehicle(Vehicle vehicle);16
17 /**18 * Remove vehicle from spot.19 * @throws SpotEmptyException If spot is not occupied20 */21 public void unparkVehicle();22}Contract Visualization
Section titled “Contract Visualization”Step 7: Handle Edge Cases
Section titled “Step 7: Handle Edge Cases”Edge cases are scenarios that might not be obvious but are important to handle.
Critical Edge Cases
Section titled “Critical Edge Cases”1. Parking Lot is Full
- Return
NoneorOptional.empty()instead of crashing - Provide clear error message
- Consider waitlist for future enhancement
2. Invalid Vehicle Type
- Validate vehicle type before processing
- Return appropriate exception
- Don’t allow parking if vehicle type not supported
3. Size Mismatch
- Check spot size compatibility before parking
- Large vehicle cannot park in small spot
- Small vehicle can park in larger spot (if allowed)
4. Invalid Ticket
- Validate ticket exists in active tickets
- Check ticket hasn’t been used already
- Handle ticket expiration (if applicable)
5. Concurrent Access
- Multiple gates accessing same system simultaneously
- Race condition: Two vehicles assigned same spot
- Solution: Thread-safe operations (synchronization)
6. Payment Failure
- What if payment fails during unpark?
- Don’t release spot if payment fails
- Retry mechanism or manual intervention
Edge Case Handling Flow
Section titled “Edge Case Handling Flow”Thread Safety Considerations
Section titled “Thread Safety Considerations”1# Simplified thread-safe parking2class ParkingLotSystem:3 def __init__(self):4 self._lock = threading.Lock()5 self.active_tickets = {}6 self.floors = []7
8 def park_vehicle(self, vehicle: Vehicle) -> Optional[ParkingTicket]:9 with self._lock: # Critical section10 spot = self._find_available_spot(vehicle)11 if not spot:12 return None13
14 # Double-check after acquiring lock15 if not spot.can_fit_vehicle(vehicle):16 return None17
18 spot.park_vehicle(vehicle)19 ticket = ParkingTicket(vehicle, spot)20 self.active_tickets[ticket.ticket_id] = ticket21 return ticket1// Simplified thread-safe parking2class ParkingLotSystem {3 private final ReentrantLock lock = new ReentrantLock();4 private Map<String, ParkingTicket> activeTickets = new HashMap<>();5 private List<ParkingFloor> floors = new ArrayList<>();6
7 public Optional<ParkingTicket> parkVehicle(Vehicle vehicle) {8 lock.lock(); // Critical section9 try {10 Optional<ParkingSpot> spotOpt = findAvailableSpot(vehicle);11 if (!spotOpt.isPresent()) {12 return Optional.empty();13 }14
15 ParkingSpot spot = spotOpt.get();16 // Double-check after acquiring lock17 if (!spot.canFitVehicle(vehicle)) {18 return Optional.empty();19 }20
21 spot.parkVehicle(vehicle);22 ParkingTicket ticket = new ParkingTicket(vehicle, spot);23 activeTickets.put(ticket.getTicketId(), ticket);24 return Optional.of(ticket);25 } finally {26 lock.unlock();27 }28 }29}Step 8: Code Implementation
Section titled “Step 8: Code Implementation”Finally, implement your design following SOLID principles and design patterns.
Complete Implementation
Section titled “Complete Implementation”1from abc import ABC, abstractmethod2from enum import Enum3from typing import Optional, List, Dict4import threading5import uuid6import time7
8# =====================9# ENUMERATION10# =====================11
12class VehicleSize(Enum):13 SMALL = "SMALL"14 MEDIUM = "MEDIUM"15 LARGE = "LARGE"16
17# =====================18# INTERFACES (Strategy Pattern)19# =====================20
21class FeeStrategy(ABC):22 """Strategy interface for fee calculation."""23
24 @abstractmethod25 def calculate_fee(self, ticket: 'ParkingTicket') -> float:26 pass27
28class ParkingStrategy(ABC):29 """Strategy interface for spot selection."""30
31 @abstractmethod32 def find_spot(self, floors: List['ParkingFloor'], vehicle: 'Vehicle') -> Optional['ParkingSpot']:33 pass34
35# =====================36# CORE ENTITIES37# =====================38
39class Vehicle(ABC):40 """Abstract base class for vehicles."""41
42 def __init__(self, license_number: str, size: VehicleSize):43 self.license_number = license_number44 self.size = size45
46 def get_size(self) -> VehicleSize:47 return self.size48
49 def get_license_number(self) -> str:50 return self.license_number51
52class Car(Vehicle):53 def __init__(self, license_number: str):54 super().__init__(license_number, VehicleSize.MEDIUM)55
56class Truck(Vehicle):57 def __init__(self, license_number: str):58 super().__init__(license_number, VehicleSize.LARGE)59
60class Bike(Vehicle):61 def __init__(self, license_number: str):62 super().__init__(license_number, VehicleSize.SMALL)63
64class ParkingSpot:65 """Represents a single parking spot."""66
67 def __init__(self, spot_id: str, spot_size: VehicleSize):68 self.spot_id = spot_id69 self.spot_size = spot_size70 self.is_occupied = False71 self.parked_vehicle: Optional[Vehicle] = None72
73 def can_fit_vehicle(self, vehicle: Vehicle) -> bool:74 """Check if vehicle can fit in this spot."""75 return (not self.is_occupied and76 self.spot_size.value >= vehicle.get_size().value)77
78 def park_vehicle(self, vehicle: Vehicle) -> None:79 """Park vehicle in this spot."""80 if not self.can_fit_vehicle(vehicle):81 raise ValueError(f"Vehicle {vehicle.get_license_number()} cannot fit in spot {self.spot_id}")82
83 self.parked_vehicle = vehicle84 self.is_occupied = True85
86 def unpark_vehicle(self) -> None:87 """Remove vehicle from spot."""88 if not self.is_occupied:89 raise ValueError(f"Spot {self.spot_id} is not occupied")90
91 self.parked_vehicle = None92 self.is_occupied = False93
94 def is_available(self) -> bool:95 return not self.is_occupied96
97class ParkingFloor:98 """Represents a parking floor/level."""99
100 def __init__(self, floor_number: int):101 self.floor_number = floor_number102 self.spots: Dict[str, ParkingSpot] = {}103
104 def add_spot(self, spot: ParkingSpot) -> None:105 """Add a parking spot to this floor."""106 self.spots[spot.spot_id] = spot107
108 def get_spots(self) -> List[ParkingSpot]:109 """Get all spots on this floor."""110 return list(self.spots.values())111
112class ParkingTicket:113 """Represents a parking ticket."""114
115 def __init__(self, vehicle: Vehicle, spot: ParkingSpot):116 self.ticket_id = str(uuid.uuid4())117 self.vehicle = vehicle118 self.spot = spot119 self.entry_timestamp = time.time()120 self.exit_timestamp: Optional[float] = None121
122 def set_exit_timestamp(self, timestamp: float) -> None:123 """Set exit timestamp when vehicle leaves."""124 self.exit_timestamp = timestamp125
126 def get_duration_in_hours(self) -> float:127 """Calculate parking duration in hours."""128 if self.exit_timestamp is None:129 return (time.time() - self.entry_timestamp) / 3600130 return (self.exit_timestamp - self.entry_timestamp) / 3600131
132# =====================133# STRATEGY IMPLEMENTATIONS134# =====================135
136class FlatRateFeeStrategy(FeeStrategy):137 """Flat rate fee calculation: $2 per hour."""138
139 RATE_PER_HOUR = 2.0140
141 def calculate_fee(self, ticket: ParkingTicket) -> float:142 hours = max(1, ticket.get_duration_in_hours())143 return hours * self.RATE_PER_HOUR144
145class NearestFirstStrategy(ParkingStrategy):146 """Find nearest available spot (first available)."""147
148 def find_spot(self, floors: List[ParkingFloor], vehicle: Vehicle) -> Optional[ParkingSpot]:149 for floor in floors:150 for spot in floor.get_spots():151 if spot.can_fit_vehicle(vehicle):152 return spot153 return None154
155# =====================156# FACTORY PATTERN157# =====================158
159class VehicleFactory:160 """Factory for creating vehicles."""161
162 @staticmethod163 def create_vehicle(size: VehicleSize, license_number: str) -> Vehicle:164 if size == VehicleSize.SMALL:165 return Bike(license_number)166 elif size == VehicleSize.MEDIUM:167 return Car(license_number)168 elif size == VehicleSize.LARGE:169 return Truck(license_number)170 else:171 raise ValueError(f"Unknown vehicle size: {size}")172
173# =====================174# SINGLETON PATTERN - MAIN SYSTEM175# =====================176
177class ParkingLotSystem:178 """Main parking lot system (Singleton)."""179
180 _instance: Optional['ParkingLotSystem'] = None181 _lock = threading.Lock()182
183 def __new__(cls):184 if cls._instance is None:185 with cls._lock:186 if cls._instance is None:187 cls._instance = super().__new__(cls)188 return cls._instance189
190 def __init__(self):191 # Prevent re-initialization192 if hasattr(self, '_initialized'):193 return194
195 self.floors: List[ParkingFloor] = []196 self.active_tickets: Dict[str, ParkingTicket] = {}197 self.fee_strategy: FeeStrategy = FlatRateFeeStrategy()198 self.parking_strategy: ParkingStrategy = NearestFirstStrategy()199 self._operation_lock = threading.Lock()200 self._initialized = True201
202 def add_floor(self, floor: ParkingFloor) -> None:203 """Add a parking floor to the system."""204 self.floors.append(floor)205
206 def set_fee_strategy(self, strategy: FeeStrategy) -> None:207 """Set the fee calculation strategy."""208 self.fee_strategy = strategy209
210 def set_parking_strategy(self, strategy: ParkingStrategy) -> None:211 """Set the parking spot selection strategy."""212 self.parking_strategy = strategy213
214 def park_vehicle(self, vehicle: Vehicle) -> Optional[ParkingTicket]:215 """216 Park a vehicle and return a ticket.217 Thread-safe operation.218 """219 with self._operation_lock:220 spot = self.parking_strategy.find_spot(self.floors, vehicle)221
222 if not spot:223 print(f"No spot available for vehicle {vehicle.get_license_number()}")224 return None225
226 # Double-check after acquiring lock227 if not spot.can_fit_vehicle(vehicle):228 print(f"Spot {spot.spot_id} no longer available")229 return None230
231 spot.park_vehicle(vehicle)232 ticket = ParkingTicket(vehicle, spot)233 self.active_tickets[ticket.ticket_id] = ticket234
235 print(f"Vehicle {vehicle.get_license_number()} parked at {spot.spot_id}")236 return ticket237
238 def unpark_vehicle(self, ticket_id: str) -> Optional[float]:239 """240 Unpark a vehicle and calculate fee.241 Thread-safe operation.242 """243 with self._operation_lock:244 if ticket_id not in self.active_tickets:245 print(f"Invalid ticket: {ticket_id}")246 return None247
248 ticket = self.active_tickets.pop(ticket_id)249 ticket.spot.unpark_vehicle()250 ticket.set_exit_timestamp(time.time())251
252 fee = self.fee_strategy.calculate_fee(ticket)253 print(f"Vehicle {ticket.vehicle.get_license_number()} unparked. Fee: ${fee:.2f}")254 return fee255
256# =====================257# DEMO258# =====================259
260if __name__ == "__main__":261 # Get singleton instance262 parking_lot = ParkingLotSystem()263
264 # Setup parking lot265 floor1 = ParkingFloor(1)266 floor1.add_spot(ParkingSpot("1-A", VehicleSize.SMALL))267 floor1.add_spot(ParkingSpot("1-B", VehicleSize.MEDIUM))268 floor1.add_spot(ParkingSpot("1-C", VehicleSize.LARGE))269 parking_lot.add_floor(floor1)270
271 # Create vehicles using factory272 car = VehicleFactory.create_vehicle(VehicleSize.MEDIUM, "ABC-123")273 truck = VehicleFactory.create_vehicle(VehicleSize.LARGE, "XYZ-999")274
275 # Park vehicles276 ticket1 = parking_lot.park_vehicle(car)277 ticket2 = parking_lot.park_vehicle(truck)278
279 # Simulate time passing280 time.sleep(2) # 2 seconds = minimal charge281
282 # Unpark vehicles283 if ticket1:284 fee1 = parking_lot.unpark_vehicle(ticket1.ticket_id)285 print(f"Fee charged: ${fee1:.2f}")1import java.util.*;2import java.util.concurrent.ConcurrentHashMap;3import java.util.concurrent.locks.ReentrantLock;4
5// =====================6// ENUMERATION7// =====================8enum VehicleSize {9 SMALL, MEDIUM, LARGE10}11
12// =====================13// INTERFACES (Strategy Pattern)14// =====================15
16interface FeeStrategy {17 double calculateFee(ParkingTicket ticket);18}19
20interface ParkingStrategy {21 Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle);22}23
24// =====================25// CORE ENTITIES26// =====================27
28abstract class Vehicle {29 protected String licenseNumber;30 protected VehicleSize size;31
32 public Vehicle(String licenseNumber, VehicleSize size) {33 this.licenseNumber = licenseNumber;34 this.size = size;35 }36
37 public VehicleSize getSize() { return size; }38 public String getLicenseNumber() { return licenseNumber; }39}40
41class Car extends Vehicle {42 public Car(String licenseNumber) {43 super(licenseNumber, VehicleSize.MEDIUM);44 }45}46
47class Truck extends Vehicle {48 public Truck(String licenseNumber) {49 super(licenseNumber, VehicleSize.LARGE);50 }51}52
53class Bike extends Vehicle {54 public Bike(String licenseNumber) {55 super(licenseNumber, VehicleSize.SMALL);56 }57}58
59class ParkingSpot {60 private String spotId;61 private VehicleSize spotSize;62 private boolean isOccupied;63 private Vehicle parkedVehicle;64
65 public ParkingSpot(String spotId, VehicleSize spotSize) {66 this.spotId = spotId;67 this.spotSize = spotSize;68 this.isOccupied = false;69 }70
71 public boolean canFitVehicle(Vehicle vehicle) {72 return !isOccupied &&73 spotSize.ordinal() >= vehicle.getSize().ordinal();74 }75
76 public void parkVehicle(Vehicle vehicle) {77 if (!canFitVehicle(vehicle)) {78 throw new IllegalArgumentException(79 "Vehicle " + vehicle.getLicenseNumber() +80 " cannot fit in spot " + spotId81 );82 }83 this.parkedVehicle = vehicle;84 this.isOccupied = true;85 }86
87 public void unparkVehicle() {88 if (!isOccupied) {89 throw new IllegalStateException("Spot " + spotId + " is not occupied");90 }91 this.parkedVehicle = null;92 this.isOccupied = false;93 }94
95 public String getSpotId() { return spotId; }96 public boolean isOccupied() { return isOccupied; }97}98
99class ParkingFloor {100 private int floorNumber;101 private Map<String, ParkingSpot> spots;102
103 public ParkingFloor(int floorNumber) {104 this.floorNumber = floorNumber;105 this.spots = new HashMap<>();106 }107
108 public void addSpot(ParkingSpot spot) {109 spots.put(spot.getSpotId(), spot);110 }111
112 public List<ParkingSpot> getSpots() {113 return new ArrayList<>(spots.values());114 }115}116
117class ParkingTicket {118 private String ticketId;119 private Vehicle vehicle;120 private ParkingSpot spot;121 private long entryTimestamp;122 private long exitTimestamp;123
124 public ParkingTicket(Vehicle vehicle, ParkingSpot spot) {125 this.ticketId = UUID.randomUUID().toString();126 this.vehicle = vehicle;127 this.spot = spot;128 this.entryTimestamp = System.currentTimeMillis();129 }130
131 public String getTicketId() { return ticketId; }132 public Vehicle getVehicle() { return vehicle; }133 public ParkingSpot getSpot() { return spot; }134
135 public long getDurationInHours() {136 long exit = exitTimestamp == 0 ? System.currentTimeMillis() : exitTimestamp;137 return (exit - entryTimestamp) / (1000 * 60 * 60);138 }139
140 public void setExitTimestamp(long timestamp) {141 this.exitTimestamp = timestamp;142 }143}144
145// =====================146// STRATEGY IMPLEMENTATIONS147// =====================148
149class FlatRateFeeStrategy implements FeeStrategy {150 private static final double RATE_PER_HOUR = 2.0;151
152 @Override153 public double calculateFee(ParkingTicket ticket) {154 long hours = Math.max(1, ticket.getDurationInHours());155 return hours * RATE_PER_HOUR;156 }157}158
159class NearestFirstStrategy implements ParkingStrategy {160 @Override161 public Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle) {162 for (ParkingFloor floor : floors) {163 for (ParkingSpot spot : floor.getSpots()) {164 if (spot.canFitVehicle(vehicle)) {165 return Optional.of(spot);166 }167 }168 }169 return Optional.empty();170 }171}172
173// =====================174// FACTORY PATTERN175// =====================176
177class VehicleFactory {178 public static Vehicle createVehicle(VehicleSize size, String licenseNumber) {179 switch (size) {180 case SMALL: return new Bike(licenseNumber);181 case MEDIUM: return new Car(licenseNumber);182 case LARGE: return new Truck(licenseNumber);183 default: throw new IllegalArgumentException("Unknown vehicle size");184 }185 }186}187
188// =====================189// SINGLETON PATTERN - MAIN SYSTEM190// =====================191
192class ParkingLotSystem {193 private static volatile ParkingLotSystem instance;194 private static final Object lock = new Object();195
196 private List<ParkingFloor> floors;197 private Map<String, ParkingTicket> activeTickets;198 private FeeStrategy feeStrategy;199 private ParkingStrategy parkingStrategy;200 private final ReentrantLock operationLock = new ReentrantLock();201
202 private ParkingLotSystem() {203 this.floors = new ArrayList<>();204 this.activeTickets = new ConcurrentHashMap<>();205 this.feeStrategy = new FlatRateFeeStrategy();206 this.parkingStrategy = new NearestFirstStrategy();207 }208
209 public static ParkingLotSystem getInstance() {210 if (instance == null) {211 synchronized (lock) {212 if (instance == null) {213 instance = new ParkingLotSystem();214 }215 }216 }217 return instance;218 }219
220 public void addFloor(ParkingFloor floor) {221 floors.add(floor);222 }223
224 public void setFeeStrategy(FeeStrategy feeStrategy) {225 this.feeStrategy = feeStrategy;226 }227
228 public void setParkingStrategy(ParkingStrategy parkingStrategy) {229 this.parkingStrategy = parkingStrategy;230 }231
232 public Optional<ParkingTicket> parkVehicle(Vehicle vehicle) {233 operationLock.lock();234 try {235 Optional<ParkingSpot> spotOpt = parkingStrategy.findSpot(floors, vehicle);236
237 if (!spotOpt.isPresent()) {238 System.out.println("No spot available for vehicle " + vehicle.getLicenseNumber());239 return Optional.empty();240 }241
242 ParkingSpot spot = spotOpt.get();243
244 // Double-check after acquiring lock245 if (!spot.canFitVehicle(vehicle)) {246 System.out.println("Spot " + spot.getSpotId() + " no longer available");247 return Optional.empty();248 }249
250 spot.parkVehicle(vehicle);251 ParkingTicket ticket = new ParkingTicket(vehicle, spot);252 activeTickets.put(ticket.getTicketId(), ticket);253
254 System.out.println("Vehicle " + vehicle.getLicenseNumber() +255 " parked at " + spot.getSpotId());256 return Optional.of(ticket);257 } finally {258 operationLock.unlock();259 }260 }261
262 public Optional<Double> unparkVehicle(String ticketId) {263 operationLock.lock();264 try {265 if (!activeTickets.containsKey(ticketId)) {266 System.out.println("Invalid ticket: " + ticketId);267 return Optional.empty();268 }269
270 ParkingTicket ticket = activeTickets.remove(ticketId);271 ticket.getSpot().unparkVehicle();272 ticket.setExitTimestamp(System.currentTimeMillis());273
274 double fee = feeStrategy.calculateFee(ticket);275 System.out.println("Vehicle " + ticket.getVehicle().getLicenseNumber() +276 " unparked. Fee: $" + fee);277 return Optional.of(fee);278 } finally {279 operationLock.unlock();280 }281 }282}283
284// =====================285// DEMO286// =====================287
288public class ParkingLotDemo {289 public static void main(String[] args) {290 ParkingLotSystem parkingLot = ParkingLotSystem.getInstance();291
292 // Setup293 ParkingFloor floor1 = new ParkingFloor(1);294 floor1.addSpot(new ParkingSpot("1-A", VehicleSize.SMALL));295 floor1.addSpot(new ParkingSpot("1-B", VehicleSize.MEDIUM));296 floor1.addSpot(new ParkingSpot("1-C", VehicleSize.LARGE));297 parkingLot.addFloor(floor1);298
299 // Create vehicles300 Vehicle car = VehicleFactory.createVehicle(VehicleSize.MEDIUM, "ABC-123");301 Vehicle truck = VehicleFactory.createVehicle(VehicleSize.LARGE, "XYZ-999");302
303 // Park vehicles304 Optional<ParkingTicket> ticket1 = parkingLot.parkVehicle(car);305 Optional<ParkingTicket> ticket2 = parkingLot.parkVehicle(truck);306
307 // Simulate time passing308 try { Thread.sleep(1000); } catch (InterruptedException e) {}309
310 // Unpark vehicles311 if (ticket1.isPresent()) {312 parkingLot.unparkVehicle(ticket1.get().getTicketId());313 }314 }315}Key Implementation Highlights
Section titled “Key Implementation Highlights”- Ensures single instance across multiple gates
- Thread-safe double-checked locking
2. Strategy Pattern:
FeeStrategy- Swappable fee calculation algorithmsParkingStrategy- Swappable spot selection algorithms
3. Factory Pattern:
VehicleFactory- Centralized vehicle creation
4. Thread Safety:
- Locks for critical sections
- Concurrent collections for shared data
5. Size Compatibility:
- Uses enum ordinal for size comparison
- Small vehicles can use larger spots
Problem: Multiple gates need to see the same spot availability.
Without Singleton:
1# Gate A2lot_a = ParkingLotSystem() # Empty spots3lot_a.park_vehicle(car1) # Parks in Spot 14
5# Gate B (different instance!)6lot_b = ParkingLotSystem() # Also empty spots7lot_b.park_vehicle(car2) # Also tries Spot 1 - COLLISION!1// Gate A2ParkingLotSystem lotA = new ParkingLotSystem(); // Empty spots3lotA.parkVehicle(car1); // Parks in Spot 14
5// Gate B (different instance!)6ParkingLotSystem lotB = new ParkingLotSystem(); // Also empty spots7lotB.parkVehicle(car2); // Also tries Spot 1 - COLLISION!With Singleton:
1# Gate A2lot_a = ParkingLotSystem.get_instance() # Same instance3lot_a.park_vehicle(car1) # Parks in Spot 14
5# Gate B6lot_b = ParkingLotSystem.get_instance() # Same instance!7lot_b.park_vehicle(car2) # Sees Spot 1 occupied, uses Spot 21// Gate A2ParkingLotSystem lotA = ParkingLotSystem.getInstance(); // Same instance3lotA.parkVehicle(car1); // Parks in Spot 14
5// Gate B6ParkingLotSystem lotB = ParkingLotSystem.getInstance(); // Same instance!7lotB.parkVehicle(car2); // Sees Spot 1 occupied, uses Spot 2Problem: Fee calculation and spot selection algorithms might change.
Without Strategy:
1class ParkingLotSystem:2 def calculate_fee(self, ticket):3 if is_weekend():4 return ticket.hours * 5.05 elif is_holiday():6 return ticket.hours * 7.07 elif ticket.vehicle.size == LARGE:8 return ticket.hours * 3.09 else:10 return ticket.hours * 2.011 # Adding new pricing = modifying this class ❌1class ParkingLotSystem {2 public double calculateFee(ParkingTicket ticket) {3 if (isWeekend()) {4 return ticket.getHours() * 5.0;5 } else if (isHoliday()) {6 return ticket.getHours() * 7.0;7 } else if (ticket.getVehicle().getSize() == LARGE) {8 return ticket.getHours() * 3.0;9 } else {10 return ticket.getHours() * 2.0;11 }12 }13 // Adding new pricing = modifying this class ❌14}With Strategy:
1class ParkingLotSystem:2 def __init__(self):3 self.fee_strategy = FlatRateFeeStrategy()4
5 def set_fee_strategy(self, strategy):6 self.fee_strategy = strategy # Swap at runtime7
8 def unpark_vehicle(self, ticket_id):9 fee = self.fee_strategy.calculate_fee(ticket) # Delegates1class ParkingLotSystem {2 private FeeStrategy feeStrategy;3
4 public ParkingLotSystem() {5 this.feeStrategy = new FlatRateFeeStrategy();6 }7
8 public void setFeeStrategy(FeeStrategy strategy) {9 this.feeStrategy = strategy; // Swap at runtime10 }11
12 public double unparkVehicle(String ticketId) {13 // ... get ticket ...14 double fee = this.feeStrategy.calculateFee(ticket); // Delegates15 return fee;16 }17}Benefits:
- Add new strategies without modifying existing code
- Test strategies independently
- Swap strategies at runtime
- Follows Open/Closed Principle
Problem: Vehicle creation logic scattered across codebase.
Without Factory:
1# Client code everywhere2if input == "car":3 vehicle = Car(license)4elif input == "truck":5 vehicle = Truck(license)6elif input == "bike":7 vehicle = Bike(license)8# Duplicated everywhere ❌1// Client code everywhere2if (input.equals("car")) {3 vehicle = new Car(license);4} else if (input.equals("truck")) {5 vehicle = new Truck(license);6} else if (input.equals("bike")) {7 vehicle = new Bike(license);8}9// Duplicated everywhere ❌With Factory:
1# Centralized creation2vehicle = VehicleFactory.create_vehicle(VehicleSize.MEDIUM, license)3# Client doesn't know about Car/Truck/Bike1// Centralized creation2Vehicle vehicle = VehicleFactory.createVehicle(VehicleSize.MEDIUM, license);3// Client doesn't know about Car/Truck/BikeSystem Flow Diagrams
Section titled “System Flow Diagrams”Parking Flow
Section titled “Parking Flow”Unparking Flow
Section titled “Unparking Flow”Extensibility & Future Enhancements
Section titled “Extensibility & Future Enhancements”Easy to Extend
Section titled “Easy to Extend”1. Add New Vehicle Type:
1class Van(Vehicle):2 def __init__(self, license_number: str):3 super().__init__(license_number, VehicleSize.LARGE)4
5# Update factory6class VehicleFactory:7 @staticmethod8 def create_vehicle(size, license):9 # ... existing code ...10 # No changes needed if size already exists!1class Van extends Vehicle {2 public Van(String licenseNumber) {3 super(licenseNumber, VehicleSize.LARGE);4 }5}6
7// Update factory8class VehicleFactory {9 public static Vehicle createVehicle(VehicleSize size, String license) {10 // ... existing code ...11 // No changes needed if size already exists!12 }13}2. Add New Fee Strategy:
1class ProgressiveFeeStrategy(FeeStrategy):2 """First hour free, then $2/hour."""3
4 def calculate_fee(self, ticket):5 hours = ticket.get_duration_in_hours()6 if hours <= 1:7 return 08 return (hours - 1) * 2.09
10# Use it11parking_lot.set_fee_strategy(ProgressiveFeeStrategy())1class ProgressiveFeeStrategy implements FeeStrategy {2 /** First hour free, then $2/hour. */3
4 @Override5 public double calculateFee(ParkingTicket ticket) {6 long hours = ticket.getDurationInHours();7 if (hours <= 1) {8 return 0;9 }10 return (hours - 1) * 2.0;11 }12}13
14// Use it15parkingLot.setFeeStrategy(new ProgressiveFeeStrategy());3. Add New Parking Strategy:
1class BestFitStrategy(ParkingStrategy):2 """Find smallest spot that fits vehicle."""3
4 def find_spot(self, floors, vehicle):5 best_spot = None6 for floor in floors:7 for spot in floor.get_spots():8 if spot.can_fit_vehicle(vehicle):9 if best_spot is None or \10 spot.spot_size.value < best_spot.spot_size.value:11 best_spot = spot12 return best_spot13
14# Use it15parking_lot.set_parking_strategy(BestFitStrategy())1class BestFitStrategy implements ParkingStrategy {2 /** Find smallest spot that fits vehicle. */3
4 @Override5 public Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle) {6 ParkingSpot bestSpot = null;7 for (ParkingFloor floor : floors) {8 for (ParkingSpot spot : floor.getSpots()) {9 if (spot.canFitVehicle(vehicle)) {10 if (bestSpot == null ||11 spot.getSpotSize().ordinal() < bestSpot.getSpotSize().ordinal()) {12 bestSpot = spot;13 }14 }15 }16 }17 return Optional.ofNullable(bestSpot);18 }19}20
21// Use it22parkingLot.setParkingStrategy(new BestFitStrategy());Future Enhancements
Section titled “Future Enhancements”1. Reservation System:
- Add
Reservationclass - Modify
ParkingSpotto track reservations - Add
ReservationStrategy
2. Payment Integration:
- Add
PaymentStrategyinterface - Implement
CreditCardPayment,CashPayment,MobilePayment
3. Access Control:
- Add
AccessCardclass - Implement VIP parking, employee parking
4. Monitoring & Analytics:
- Add
ParkingLotObserver(Observer pattern) - Track occupancy rates, revenue, peak hours
Summary
Section titled “Summary”Key Takeaways
Section titled “Key Takeaways”- Follow Systematic Approach - Don’t jump to code
- Identify Actors First - Who uses the system?
- Identify Entities - What are the core objects?
- Assign Responsibilities - Single Responsibility Principle
- Design Class Diagrams - Visualize structure and relationships
- Define Contracts - Clear interfaces and APIs
- Handle Edge Cases - Think about errors and concurrency
- Use Design Patterns - Singleton, Strategy, Factory
- Make it Extensible - Easy to add new features
- Singleton - Single system instance
- Strategy - Swappable algorithms (fee, parking)
- Factory - Centralized object creation
- Inheritance - Vehicle hierarchy
Best Practices Demonstrated
Section titled “Best Practices Demonstrated”- SOLID principles (SRP, OCP)
- Thread safety considerations
- Clear separation of concerns
- Extensible design
- Error handling
- Clean code structure
Next Steps
Section titled “Next Steps”Now that you’ve mastered the Parking Lot System:
Practice Similar Problems:
- Elevator System
- Library Management System
- Restaurant Management System
- ATM System
Explore More Patterns:
- Observer Pattern (for notifications)
- Command Pattern (for operations)
- State Pattern (for spot states)
Deepen Your Understanding:
- Read about concurrency patterns
- Study database design for persistence
- Learn about distributed systems