Parking lot

Vehicles, spots, tickets and fees: a classic exercise in allocation, pricing rules and concurrent entry gates.

On the roadmap:Software Design · step 4: Low-level design: start with objects

Lesson 2 of 16 · 11 min read · quiz · exercise

The problem

Design a parking lot with several levels. It accepts motorcycles, cars and buses; assigns the nearest suitable free spot on entry; tracks occupancy; and charges a fee on exit by duration and vehicle type. Several entry and exit gates work at once.

Entities

ClassResponsibility
ParkingLotOwns the levels and gates; entry point for park and unpark
LevelA floor holding spots; answers "which free spot fits this vehicle"
ParkingSpotHas a type and occupied state; can be reserved and freed
VehicleBase type with subclasses or a type field (motorcycle, car, bus)
TicketCreated on entry: vehicle, spot, entry time
PricingStrategyTurns a stay (duration, vehicle type) into a fee

Figure SDA-LLD-02-1 · Parking lot class structure · © forcsguy.com

The entry and exit flow

Figure SDA-LLD-02-2 · Entry and exit sequence · © forcsguy.com

Key code

type SpotType = "MOTORCYCLE" | "COMPACT" | "LARGE";
const fits: Record<string, SpotType[]> = {
  MOTORCYCLE: ["MOTORCYCLE", "COMPACT", "LARGE"],
  CAR: ["COMPACT", "LARGE"],
  BUS: ["LARGE"],
};

class ParkingSpot {
  occupied = false;
  constructor(readonly id: string, readonly type: SpotType) {}
  tryReserve(): boolean {        // atomic in a real system (lock or compare-and-set)
    if (this.occupied) return false;
    this.occupied = true; return true;
  }
  free() { this.occupied = false; }
}

class Level {
  constructor(readonly spots: ParkingSpot[]) {}
  findAndReserve(vehicleType: string): ParkingSpot | null {
    for (const s of this.spots)
      if (fits[vehicleType].includes(s.type) && s.tryReserve()) return s;
    return null;
  }
}

The important details are tryReserve (one winner per spot) and the fits table (compatibility in one place).

Pricing as a strategy

interface PricingStrategy { fee(vehicleType: string, minutes: number): number }
const hourly: PricingStrategy = {
  fee: (t, m) => Math.ceil(m / 60) * ({ MOTORCYCLE: 1, CAR: 2, BUS: 5 } as any)[t],
};

Swap in a daily cap, a weekend rate or a subscriber discount without touching ParkingLot.

Before: pricing hard-coded in the ticket

After: pricing as a strategy

What changed

  • A new fee rule is a new class, not an edit to old code
  • Each pricing rule can be tested alone
  • The ticket no longer knows how fees are calculated

Edge cases and extensions

  • Lot full: park returns "no spot" cleanly rather than throwing.
  • Lost ticket: look the vehicle up by plate; charge the maximum rate.
  • Nearest spot: keep free spots in a sorted structure per level (a heap or a set per type) so allocation is not a linear scan at scale.
  • Display boards that show free counts are observers of spot changes.
  • Persistence: if the app restarts, rebuild occupancy from stored tickets.

Real-world reading

Check your understanding

Question 1 of 3

0 correct so far

Which class should decide the fee for a stay?

Go deeper

© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.