System Design
Object-Oriented Design
Parking Lot

Design Example: The Parking Lot

Designing a Parking Lot is a classic OOD problem. Let's apply our 4-step framework to build a robust, scalable system.


Step 1: Requirements Gathering

In an interview, you must clarify the scope. Here are the key questions and clarified requirements:

  • Capacity: How many floors and spots?
    • Clarified: Multiple floors, each with varying capacities.
  • Vehicle Types: What vehicles should we support?
    • Clarified: Motorbikes, Cars, and Trucks.
  • Spot Types: Do spots have sizes?
    • Clarified: Small (for bikes), Medium (for cars), and Large (for trucks).
  • Payment: How is the fee calculated?
    • Clarified: Hourly rate based on vehicle type.

Core Use Cases:

  1. A driver can get a ticket and enter the parking lot.
  2. The system finds the nearest available spot for the vehicle type.
  3. A driver can pay the fee and exit.
  4. Admin can add/remove floors or spots.

Step 2: Identify Core Objects

  • ParkingLot: A Singleton managing the entire system.
  • ParkingFloor: Manages a collection of spots.
  • ParkingSpot: An abstract base for SmallSpot, MediumSpot, and LargeSpot.
  • Vehicle: An abstract base for Motorbike, Car, and Truck.
  • Ticket: Represents the entry/exit record.
  • PaymentManager: Handles fee calculation and processing.

Step 3: Design Class Diagram


Step 4: Implementation in TypeScript

enum VehicleType {
  MOTORBIKE,
  CAR,
  TRUCK
}
 
// 1. Vehicle Abstraction
abstract class Vehicle {
  constructor(public id: string, public type: VehicleType) {}
}
 
class Car extends Vehicle {
  constructor(id: string) { super(id, VehicleType.CAR); }
}
 
// 2. Parking Spot Abstraction
abstract class ParkingSpot {
  protected occupied: boolean = false;
  constructor(public id: string, public type: VehicleType) {}
 
  isAvailable(): boolean { return !this.occupied; }
  park(): void { this.occupied = true; }
  unpark(): void { this.occupied = false; }
}
 
class CompactSpot extends ParkingSpot {
  constructor(id: string) { super(id, VehicleType.CAR); }
}
 
// 3. Parking Floor
class ParkingFloor {
  constructor(public id: string, private spots: ParkingSpot[]) {}
 
  findAvailableSpot(type: VehicleType): ParkingSpot | undefined {
    return this.spots.find(s => s.isAvailable() && s.type === type);
  }
}
 
// 4. Parking Lot (Singleton)
class ParkingLot {
  private static instance: ParkingLot;
  private floors: ParkingFloor[] = [];
 
  private constructor() {}
 
  static getInstance(): ParkingLot {
    if (!this.instance) this.instance = new ParkingLot();
    return this.instance;
  }
 
  addFloor(floor: ParkingFloor) { this.floors.push(floor); }
 
  parkVehicle(vehicle: Vehicle): boolean {
    for (const floor of this.floors) {
      const spot = floor.findAvailableSpot(vehicle.type);
      if (spot) {
        spot.park();
        console.log(`Vehicle ${vehicle.id} parked at spot ${spot.id}`);
        return true;
      }
    }
    return false;
  }
}

Advanced Design Patterns

To make the design even more robust, we can apply:

1. Strategy Pattern (For Pricing)

Instead of hardcoding pricing in the PaymentManager, use a Strategy pattern to handle different pricing rules (e.g., Weekday vs. Weekend rates).

interface PricingStrategy {
  calculatePrice(hours: number): number;
}
 
class FlatPricing implements PricingStrategy {
  calculatePrice(hours: number) { return hours * 20; }
}

2. Facade Pattern (For User Interaction)

Provide a simple interface for the user to interact with the complex internal system.

class ParkingFacade {
  private lot = ParkingLot.getInstance();
 
  requestEntry(licensePlate: string, type: VehicleType) {
    const vehicle = new Car(licensePlate); // Simplified
    return this.lot.parkVehicle(vehicle);
  }
}

Wrap Up

Designing a Parking Lot tests your ability to handle object relationships, use design patterns, and think about scalability (e.g., adding Electric Chargers or Valet service).

[!TIP] Always start with Requirement Clarification before writing a single line of code. This demonstrates that you are a thoughtful engineer who understands the problem first.