Designing a Parking Lot System is a classic object-oriented design (OOD) problem often asked in interviews. The system should be scalable, easy to manage, and capable of handling different scenarios. Here's an in-depth solution including the architecture, features, classes, and math calculations for capacity planning.
1. Problem Definition
We need to design a parking lot system that handles:
- Parking and retrieving vehicles.
- Managing different vehicle types (car, motorcycle, truck, etc.).
- Multiple parking lot levels.
- Payment and billing system (optional).
2. Functional and Non-Functional Requirements
Functional Requirements:
- Add/Remove Parking Levels: The system should support multiple levels (or floors).
- Track Available Spots: The system should track available parking spots on each level.
- Vehicle Types: The system must differentiate between types of vehicles (car, motorcycle, truck, etc.).
- Parking Fees: Calculate parking fees based on duration (optional feature).
- Issue Ticket: Upon entry, the system issues a parking ticket with the parking spot.
- Retrieve Vehicle: Users can retrieve vehicles and pay for parking (if applicable).
Non-Functional Requirements:
- Scalability: The system should handle many vehicles in a large parking lot with multiple levels.
- Concurrency: The system should allow multiple vehicles to enter/exit at the same time without delays.
- High Availability: Ensure the system has minimal downtime.
- Performance: The system should process vehicle parking and retrieval in real time.
3. Key Entities and Classes
We will define several classes to model the system. Here are the key entities and relationships.
a) Vehicle Class
Each vehicle will have properties like a license plate, size, and the time it entered the parking lot.
type Vehicle struct {
LicensePlate string
VehicleType string // motorcycle, car, truck
Size int // Size: 1 for motorcycle, 2 for car, 3 for truck
}
b) Parking Spot Class
A parking spot will have a specific size, and it will only allow vehicles that fit. Each spot belongs to a parking level.
type ParkingSpot struct {
SpotID string
SpotSize int // Size of spot: 1 (motorcycle), 2 (car), 3 (truck)
IsFree bool // Tracks whether the spot is available
Level int // Parking level
}
c) Parking Level Class
A parking level consists of multiple parking spots and manages the spots on the level.
type ParkingLevel struct {
LevelID int
TotalSpots int
AvailableSpots int
ParkingSpots []ParkingSpot
}
d) Parking Lot Class
The ParkingLot
class will manage multiple levels and orchestrate the entire system.
type ParkingLot struct {
Levels []ParkingLevel
}
func (lot *ParkingLot) ParkVehicle(v Vehicle) (bool, string) {
for _, level := range lot.Levels {
spotID := level.FindSpotForVehicle(v)
if spotID != "" {
level.OccupySpot(spotID)
return true, spotID
}
}
return false, ""
}
func (lot *ParkingLot) FreeSpot(levelID int, spotID string) {
lot.Levels[levelID].FreeSpot(spotID)
}
4. Parking Rules
- Motorcycles can park in any spot (1, 2, or 3).
- Cars can park in spots with size 2 or larger.
- Trucks can only park in spots with size 3.
5. Mathematical Calculations: Capacity Planning
a) Parking Lot Size
Let’s assume we need to calculate the number of spots per level and the total capacity for a parking lot.
- Assume each level has:
- 50 motorcycle spots (size 1).
- 150 car spots (size 2).
- 20 truck spots (size 3).
For a parking lot with 10 levels:
- Total spots per level = 50 (motorcycle) + 150 (car) + 20 (truck) = 220 spots/level.
- Total spots in the entire parking lot = 220 spots/level * 10 levels = 2200 spots.
b) Probability of Spot Usage
Let’s assume the following probabilities of vehicle arrivals:
- Motorcycles: 15% of vehicles.
- Cars: 70% of vehicles.
- Trucks: 15% of vehicles.
For every 100 vehicles entering:
- 15 motorcycles: These will occupy 15 motorcycle spots (size 1).
- 70 cars: These will occupy 70 car spots (size 2).
- 15 trucks: These will occupy 15 truck spots (size 3).
Given the above parking structure:
- Motorcycle spots: 50 spots/level * 10 levels = 500 spots total.
- Car spots: 150 spots/level * 10 levels = 1500 spots total.
- Truck spots: 20 spots/level * 10 levels = 200 spots total.
This distribution ensures that the system can handle most expected traffic based on the vehicle types.
6. Billing and Payment System (Optional)
If we want to add a billing system, we need to track the time when a vehicle enters and exits the parking lot to calculate the fee.
We can use the following fee structure:
- Motorcycles: ₹10 per hour.
- Cars: ₹20 per hour.
- Trucks: ₹30 per hour.
When a vehicle enters, we store the entry time. When the vehicle exits, we calculate the parking duration and multiply by the rate.
type Ticket struct {
Vehicle Vehicle
EntryTime time.Time
ExitTime time.Time
}
func (t *Ticket) CalculateFee() float64 {
duration := t.ExitTime.Sub(t.EntryTime).Hours()
rate := 0.0
switch t.Vehicle.VehicleType {
case "motorcycle":
rate = 10.0
case "car":
rate = 20.0
case "truck":
rate = 30.0
}
return rate * duration
}
7. Concurrency and Scalability
a) Concurrency Handling
Multiple vehicles can enter/exit simultaneously. Therefore, it is essential to handle concurrency in a thread-safe manner.
- Synchronization: Use mutex locks to ensure that multiple threads don’t update parking spots simultaneously.
- Atomic Operations: For spot allocation, ensure the operation of assigning a spot and marking it as occupied is atomic to prevent race conditions.
b) Scaling Horizontally
- For very large parking lots, we can scale horizontally by having multiple
ParkingLot
instances distributed across different geographic locations. Each parking lot instance would manage its own levels and spots.
8. Optimizing System Performance
a) Caching
For large-scale parking lots, caching can be employed to store the most frequently used data, like available spots on each level. This reduces the number of database queries and improves the system's responsiveness.
b) Database Partitioning
For large systems, the parking lot data can be partitioned by levels. Each level can be stored in a different partition to balance the load and improve read/write performance.
9. Failure Handling
- Database Failover: Implement database replication to ensure that if the primary database fails, the replica can take over.
- High Availability: Use a load balancer to distribute incoming requests across multiple instances of the system to handle traffic spikes and server failures.