# Parking Lot

Course: [Low-Level Design](https://www.devtutorials.ai/learn/system-design/lld) · Section: LLD Case Studies
Source: https://www.devtutorials.ai/learn/system-design/lld/case-studies/parking-lot

> Design a parking lot: spot sizes, the smallest spot that fits, tickets and fees, then build it against a hidden test suite.

Car parks are commonly used in shopping centres, airports, and office buildings. When a vehicle enters, the system assigns a suitable parking space and records its entry time. When the vehicle leaves, the system calculates the parking fee and makes the space available again.

Although the process appears simple, designing it requires clear decisions about vehicles, parking spaces, tickets, fees, and responsibilities.

In this case study, you will design a car park management system for QuickBite’s new cloud kitchen. Delivery riders will park motorbikes, while suppliers will park larger vehicles such as trucks.

**In simple words:** we will turn a real car park into a few small classes, each with one job, and prove it works with hidden tests.

## What You Will Learn

In this case study, you will:

1. Turn a vague problem into a clear list of requirements.
2. Find the classes by looking for nouns.
3. Choose the smallest free spot that fits a vehicle.
4. Price a stay with a ticket and a pricing strategy.
5. Build the whole lot in the sandbox and pass the hidden tests.

Every case study follows the same four steps: **requirements**, **classes**, **key parts**, then a **lab** with hidden tests. Practice the steps, not just this answer. They work for any LLD interview question.

## Step 1: Agree on the Requirements

Before designing classes or writing code, first define exactly what the system must do. Clear requirements help you avoid unnecessary complexity and ensure everyone understands the scope of the solution. In an interview, it is a good practice to state these requirements aloud and confirm them before starting the design.

### What It Must Do

- Support three parking spot sizes: small, medium, and large.
- Allow bikes to park in any spot size.
- Allow cars to park only in medium or large spots.
- Allow trucks to park only in large spots.
- Always assign the smallest available spot that can accommodate the vehicle.
- Generate a parking ticket containing the allocated spot and the vehicle's entry time.
- When the vehicle leaves, release the occupied spot and calculate the parking fee.
- Charge 20 per hour, round partial hours up to the next hour, and charge for at least one hour.
- Reject a vehicle if there is no suitable available spot.
- Prevent the same vehicle registration number from being parked more than once at the same time.

### What We Leave Out

To keep the design focused and manageable, the following features are intentionally excluded:

- Payment processing such as cards, UPI, or wallets (these could be added later using a separate payment strategy).
- Multiple entry and exit gates.
- Advance parking space reservations.

Leaving features out deliberately is an important design skill. It helps you build a clear and complete first version of the system while making it easy to discuss future enhancements if needed.

## Step 2: Find the Classes

A simple trick: underline the **nouns** in the requirements. Most of them become classes. The **verbs** become methods.

| Class | Its one job |
|---|---|
| Spot | Knows its id, its size and which plate is in it |
| Ticket | Remembers the plate, the spot and the entry time |
| ParkingLot | Finds spots, issues tickets and works out fees |
| Pricing | A strategy that turns hours into a fee |

<LldDiagram caption="The class diagram for this design." code={"classDiagram\n  class ParkingLot {\n    -spots: Spot[]\n    -tickets: Map\n    +park(vehicle, hour) string\n    +leave(plate, hour) number\n    +free(size) number\n  }\n  class Spot {\n    +id: string\n    +size: Size\n    +plate: string\n  }\n  class Ticket {\n    +plate: string\n    +inHour: number\n  }\n  class Pricing {\n    <<interface>>\n    +fee(hours) number\n  }\n  ParkingLot *-- Spot : owns\n  ParkingLot *-- Ticket : issues\n  Ticket --> Spot : for\n  ParkingLot --> Pricing : uses"} />

## Step 3: Build the Key Parts

### Which Spot Fits Which Vehicle

A bike fits anywhere, a car needs a medium or large spot, and a truck needs a large one. Put that in a **table**, not an if-chain.

The table lists the sizes in order, smallest first, so small vehicles do not waste big spots:

| Vehicle | Fits (smallest first) |
|---|---|
| bike | small, medium, large |
| car | medium, large |
| truck | large |

Here is the code:

```ts runnable title="A table says which spots each vehicle fits"
type Size = "small" | "medium" | "large";
type Kind = "bike" | "car" | "truck";

// Smallest first, so small vehicles do not waste big spots.
const FITS: Record<Kind, Size[]> = {
  bike: ["small", "medium", "large"],
  car: ["medium", "large"],
  truck: ["large"],
};

console.log("car fits:", FITS.car.join(", "));
console.log("truck fits:", FITS.truck.join(", "));
```

Expected output:

```text
car fits: medium, large
truck fits: large
```

With a table, a new vehicle kind (say, an electric scooter) is one new line.

### The Spot

A `Spot` has an id, a size and a plate. The plate is empty when the spot is free. Here M1 holds car KA01AB1234:

Here is the same idea in code:

```ts runnable title="Spots know their size and who is in them"
type Size = "small" | "medium" | "large";

class Spot {
  plate: string | null = null;
  constructor(readonly id: string, readonly size: Size) {}
  get free(): boolean { return this.plate === null; }
}

const spots = [new Spot("S1", "small"), new Spot("M1", "medium"), new Spot("M2", "medium")];
spots[1].plate = "KA01AB1234";

const freeMedium = spots.filter((s) => s.size === "medium" && s.free).map((s) => s.id);
console.log(freeMedium);
```

Expected output:

```text
[
  "M2"
]
```

### Finding a Spot

Walk the sizes in the vehicle's order, smallest first, and take the first free spot.

Step through three searches. The lot has S1 (small, taken by B1), M1 (medium, free) and L1 (large, free):

Here is the same idea in code:

```ts runnable title="Find the smallest free spot that fits"
type Size = "small" | "medium" | "large";
type Kind = "bike" | "car" | "truck";
const FITS: Record<Kind, Size[]> = { bike: ["small", "medium", "large"], car: ["medium", "large"], truck: ["large"] };
type Spot = { id: string; size: Size; plate: string | null };

function findSpot(spots: Spot[], kind: Kind): Spot | null {
  for (const size of FITS[kind]) {
    const spot = spots.find((s) => s.size === size && s.plate === null);
    if (spot) return spot;
  }
  return null;
}

const spots: Spot[] = [
  { id: "S1", size: "small", plate: "B1" },
  { id: "M1", size: "medium", plate: null },
  { id: "L1", size: "large", plate: null },
];
console.log(findSpot(spots, "bike")?.id);
console.log(findSpot(spots, "truck")?.id);
console.log(findSpot(spots.slice(0, 1), "car"));
```

Expected output:

```text
M1
L1
null
```

The bike skipped the taken `S1` and moved up to `M1`. A car with no medium or large spot gets `null`.

**Easy way to remember the design:** a **Spot** knows its size and who is in it. A **Ticket** knows who came in and when. The **ParkingLot** finds spots and prices tickets. Three classes, one job each.

### The Ticket and the Fee

The ticket stores the plate, the spot and the entry hour. Step through three exits for a car that came in at hour 9, with a rate of 20 an hour:

| Leaves at | Time parked | Hours charged | Fee |
|---|---|---|---|
| 9.25 | 15 minutes | 1 | 20 |
| 11 | 2 hours | 2 | 40 |
| 11.5 | 2.5 hours | 3 | 60 |

Here is the same idea in code:

```ts runnable title="The ticket remembers when the car came in"
class Ticket {
  constructor(readonly plate: string, readonly spotId: string, readonly inHour: number) {}

  fee(outHour: number, ratePerHour: number): number {
    const hours = Math.max(1, Math.ceil(outHour - this.inHour));
    return hours * ratePerHour;
  }
}

const t = new Ticket("KA01AB1234", "M1", 9);
console.log(t.fee(9.25, 20)); // 15 minutes: still one hour
console.log(t.fee(11, 20));   // exactly 2 hours
console.log(t.fee(11.5, 20)); // 2.5 hours: rounds up to 3
```

Expected output:

```text
20
40
60
```

### Pricing as a Strategy

Malls change their prices: first hour free, weekend rates, a monthly pass. Pricing is a perfect **strategy**.

Step through one 2.5-hour stay priced two ways. `Hourly(20)` charges **60**; `FirstHourFree(20)` charges **40**:

Here is the same idea in code:

```ts runnable title="Pricing as a strategy, so weekend rates are a new class"
interface Pricing {
  fee(hours: number): number;
}
class Hourly implements Pricing {
  constructor(private readonly rate: number) {}
  fee(hours: number): number { return Math.max(1, Math.ceil(hours)) * this.rate; }
}
class FirstHourFree implements Pricing {
  constructor(private readonly rate: number) {}
  fee(hours: number): number { return Math.max(0, Math.ceil(hours) - 1) * this.rate; }
}

for (const p of [new Hourly(20), new FirstHourFree(20)]) console.log(p.fee(2.5));
```

Expected output:

```text
60
40
```

Keep money in whole units (rupees or paise), not floating-point fractions. `0.1 + 0.2` is not exactly `0.3` in JavaScript, and a parking lot that charges 59.999999 rupees will get complaints.

## Lab: Build It

Build the `ParkingLot` class. The hidden tests park bikes, cars and trucks, check which spots they get, and check the fees.

1. Make `Spot` and `Ticket` classes, and create the spots in the constructor with ids like `S1`, `M2`, `L1`.
2. Write `park`: refuse a plate that is already parked, then take the smallest free spot that fits, or throw if none fits.
3. Write `leave`: free the spot and return the fee. Write `free(size)`.

Open the lab in the sandbox. Run the tests after each step. A failing test names the situation that broke, not the test code.

## Predict the Output

<CsPredict id="park-1" code={"var fits = new Dictionary<string, string[]>\n{\n    [\"bike\"] = new[] { \"small\", \"medium\", \"large\" },\n    [\"car\"] = new[] { \"medium\", \"large\" },\n};\nvar free = new[] { \"large\", \"small\" };\nforeach (var kind in new[] { \"bike\", \"car\" })\n    Console.WriteLine(fits[kind].First(s => free.Contains(s)));"} expected={"small\nlarge"} why="The bike checks small first, and small is free. The car checks medium first, which is not free, then large, which is." />

## Common Mistakes

| Mistake | Wrong | Correct | Why it happens |
|---|---|---|---|
| If-chain per vehicle | if (kind === "car") ... else if (kind === "bike") ... | A FITS table from kind to sizes. | It is the first thing that comes to mind. |
| Biggest spot first | A bike takes L1 while S1 is free. | Smallest free spot that fits. | Looping over spots in the order they were created. |
| Fee rounds down | 2.5 hours costs 40. | Math.ceil, at least 1 hour. | Math.floor looks like the normal way to get whole hours. |
| Spot not freed | leave() returns a fee but the spot stays taken. | Clear the plate on the spot. | The test for leave only checked the fee. |
| God class | ParkingLot also prints receipts and sends SMS. | Keep ParkingLot about spots and tickets. | Everything seems related to parking. |

## Practice

## What to Remember

- Start with **requirements**, and say what you leave out.
- **Nouns** become classes; **verbs** become methods.
- A vehicle takes the **smallest free spot** it fits, using a table.
- The **ticket** remembers the entry time; the fee rounds **up**.
- Pricing is a **strategy**, so new rules are new classes.

## Further Reading

These are the sources used to check this case study:

- [Refactoring.Guru: Strategy](https://refactoring.guru/design-patterns/strategy): the pricing strategy.
- [TypeScript Handbook: Classes](https://www.typescriptlang.org/docs/handbook/2/classes.html): classes, readonly fields and constructors.
- [MDN: Math.ceil](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil): rounding up hours.

Next: **Elevator**. You will design an elevator that serves floors in a fair, sensible order.
