Parking Lot
By the Syncfusion DevTutorials team · Last updated
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:
- Turn a vague problem into a clear list of requirements.
- Find the classes by looking for nouns.
- Choose the smallest free spot that fits a vehicle.
- Price a stay with a ticket and a pricing strategy.
- 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 |
The class diagram for this design.
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:
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:
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:
Three spots. Only M2 is a free medium spot.
Here is the same idea in code:
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:
[
"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:
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:
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:
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:
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:
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:
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.
Predict the Output
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: the pricing strategy.
- TypeScript Handbook: Classes: classes, readonly fields and constructors.
- MDN: Math.ceil: rounding up hours.
Next: Elevator. You will design an elevator that serves floors in a fair, sensible order.