System design: Design a parking lot system

Jayaprasanna Roddam - Oct 8 - - Dev Community

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:

  1. Parking and retrieving vehicles.
  2. Managing different vehicle types (car, motorcycle, truck, etc.).
  3. Multiple parking lot levels.
  4. 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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .