Technology · ssh / indianDevelopers
LLD 01 — Design a Parking Lot, Part 1: Requirements and Object Model
A Parking Lot looks like a simple LLD problem until multiple vehicle types, spot allocation, tickets, pricing and concurrent entries appear. Build it step by step instead of memorising a class diagram.
A parking lot is one of those Low-Level Design problems that looks almost insultingly simple at first.
A vehicle comes in. Find an empty spot. Give the driver a ticket.
Then a few details appear.
What if a truck cannot fit into every spot? What if the building has several floors? What if two entrance gates receive cars at almost the same instant? What happens when pricing changes without changing anything about parking allocation?
At that point, the problem stops being about drawing boxes called Car, ParkingSpot and ParkingLot.
It becomes a question of where state should live, which object should be allowed to change it, and which pieces of behaviour need to evolve independently.
That is what we will design in this first part.
Problem source: This article uses an original Hyive formulation of the classic Parking Lot Low-Level Design exercise. It is not a reproduction of LeetCode's Design Parking System, which is a separate and much smaller fixed-capacity problem. The requirements, diagrams, design decisions and implementation in this series are our own.
What Are We Actually Building?
Before creating classes, we need to define the version of the parking lot we care about.
Our parking lot contains multiple floors and supports three vehicle categories:
motorcycles;
cars;
trucks.
It also contains three kinds of parking spots:
motorcycle spots;
compact spots;
large spots.
When a vehicle enters, the system should find a compatible available spot and issue a parking ticket.
When the vehicle leaves, the system should calculate the fee, close the parking session and make the spot available again.
There is one requirement that is easy to overlook:
Two simultaneous entrance requests must never successfully claim the same physical parking spot.
For now, we are deliberately leaving out payment gateways, reservations, licence-plate recognition, EV charging, persistent databases and coordination across multiple parking facilities.
Those features may be useful later, but designing all of them now would hide the actual LLD problem.
Start With Responsibilities, Not Classes
There are five obvious pieces of domain state.
Vehicle represents the thing entering the parking lot.
ParkingSpot represents a physical spot whose occupancy changes.
ParkingFloor groups parking spots.
ParkingTicket represents one parking session.
ParkingLot coordinates the workflow.
Then there are two questions that do not feel like entities at all:
Which spot should be chosen?
and:
How should the parking fee be calculated?
Those are policies.
They can change while the physical structure of the parking lot remains the same.
That gives us two useful abstractions:
SpotAllocationStrategy
PricingStrategyA useful distinction is:
Domain objects own state. Strategies encapsulate rules that are expected to vary.

The diagram already tells us something important.
ParkingLot coordinates.
ParkingSpot protects occupancy.
ParkingTicket owns session information.
Allocation and pricing are replaceable policies.
Do We Need Separate Car, Motorcycle and Truck Classes?
A common first attempt creates this hierarchy:
Vehicle
├── Motorcycle
├── Car
└── TruckThat looks object-oriented, but inheritance should exist for a reason.
Ask what behaviour is actually different.
In our current requirements, a vehicle only needs:
registration numbervehicle type
The vehicle type affects spot compatibility and pricing, but a Car object itself does not yet behave differently from a Truck object.
A simple enum is enough:
enum VehicleType {
MOTORCYCLE,
CAR,
TRUCK
}and the vehicle can remain straightforward:
final class Vehicle {
private final String registrationNumber;
private final VehicleType type;
Vehicle(
String registrationNumber,
VehicleType type) {
this.registrationNumber = registrationNumber;
this.type = type;
}
String registrationNumber() {
return registrationNumber;
}
VehicleType type() {
return type;
}
}If trucks later acquire genuinely different behaviour, the model can evolve.
For now, subclasses would mostly add ceremony.
The broader principle is useful far beyond this problem:
Do not introduce inheritance merely because your domain contains categories. Introduce it when the objects actually need different behaviour.
Parking Spots Own the Important State
Now consider the most important mutable state in the system.
A parking spot can either be empty or contain one vehicle.
enum SpotType {
MOTORCYCLE,
COMPACT,
LARGE
}The compatibility rules for our example are:
Motorcycle → motorcycle, compact or large
Car → compact or large
Truck → large
That gives us:
boolean canFit(Vehicle vehicle) {
return switch (vehicle.type()) {
case MOTORCYCLE -> true;
case CAR ->
type == SpotType.COMPACT
|| type == SpotType.LARGE;
case TRUCK ->
type == SpotType.LARGE;
};
}There is a more subtle question:
How should a vehicle claim the spot?
A dangerous design would expose two independent operations:
if (spot.isAvailable()) {
spot.occupy(vehicle);
}Imagine two entrance requests running concurrently.
Gate A checks C12 → availableGate B checks C12 → available
Gate A occupies C12Gate B occupies C12The problem is not that either availability check was wrong.The problem is that checking and changing the state were separate actions.
Instead, the spot should expose:
spot.tryOccupy(vehicle);
which conceptually performs:
CHECK COMPATIBILITY+CHECK AVAILABILITY+CHANGE STATEas one protected operation.

This gives the object ownership of its central invariant:
One parking spot can contain at most one vehicle.
For an in-memory Java implementation, we can protect this transition with synchronization.
Later, if the application runs across multiple servers, the invariant stays the same even though the mechanism enforcing it will have to change.
What Should a Parking Floor Do?
A floor mostly represents structure.
final class ParkingFloor {
private final String id;
private final int level;
private final List<ParkingSpot> spots;
ParkingFloor(
String id,
int level,
List<ParkingSpot> spots) {
this.id = id;
this.level = level;
this.spots = List.copyOf(spots);
}
String id() {
return id;
}
int level() {
return level;
}
List<ParkingSpot> spots() {
return spots;
}
}We could place:
findAvailableSpot()inside this class.
For the first requirement that would work.
Then an interviewer might say:
Prefer spots closest to the entrance.
Or:
Trucks should use higher floors before consuming large spots on the ground floor.
Or:
Keep ten compact spots reserved during peak hours.
The floor did not fundamentally change.
The selection rule did.
That is why spot allocation belongs in a separate strategy.
Spot Allocation Is a Policy
We can define:
interface SpotAllocationStrategy {
List<ParkingSpot> candidates(
Vehicle vehicle,
List<ParkingFloor> floors
);
}Our first implementation can simply traverse floors from lowest to highest and return compatible available candidates.
final class FirstAvailableSpotStrategy
implements SpotAllocationStrategy {
@Override
public List<ParkingSpot> candidates(
Vehicle vehicle,
List<ParkingFloor> floors) {
return floors.stream()
.sorted(
Comparator.comparingInt(
ParkingFloor::level
)
)
.flatMap(
floor -> floor.spots().stream()
)
.filter(
spot -> spot.canFit(vehicle)
)
.filter(
ParkingSpot::isAvailable
)
.toList();
}
}Why return several candidates instead of only one?
Because:
AVAILABLE WHEN SELECTED
does not necessarily mean:
STILL AVAILABLE WHEN CLAIMED
Two entrance requests may receive the same first candidate.
The final authority remains:
spot.tryOccupy(vehicle);
If one request loses the race, it can move to the next candidate.
This distinction will become important when we implement the complete workflow in Part 2.
Pricing Is Another Independent Rule
Pricing has the same architectural shape.
Suppose today's rules are:
Motorcycle → 20 per hourCar → 40 per hourTruck → 80 per hour
Tomorrow the parking operator might introduce:
first 30 minutes freeweekend pricinglong-stay slabspremium-floor rates
That change should not require rewriting parking allocation.
So pricing becomes another strategy:
interface PricingStrategy {
long calculateFee(
VehicleType vehicleType,
Instant entryTime,
Instant exitTime
);
}A simple hourly implementation can calculate the duration, round partial hours upward, and apply a rate for the stored vehicle type.
The important design point is not the exact numbers.
It is this:
Parking allocation and parking pricing should be able to evolve independently.
That is a genuine use of the Strategy Pattern rather than a pattern added just to make an LLD answer look sophisticated.
What Does the Ticket Represent?
A Vehicle and a ParkingTicket represent very different lifetimes.
The vehicle may exist for years.
A ticket exists for one visit.
Conceptually it contains:
ticket idvehicle registrationvehicle typespot identry timeexit time
One detail matters here.
The ticket should remember the vehicle type that entered.
We should not design an exit API like:
exit(ticketId, VehicleType.CAR);
because the caller could pass incorrect information.
The parking session already knows which vehicle entered.
That stored session state should be used during pricing.
This eventually lets us make the public operation much simpler:
exit(ticketId);
The system can recover everything else from the ticket.
Where We Are So Far
Our design now has a clear separation of responsibility:
ParkingLot
│
├── ParkingFloor
│ └── ParkingSpot
│ └── current Vehicle
│
├── SpotAllocationStrategy
│
├── PricingStrategy
│
└── ParkingTicketBut we have intentionally avoided pretending that disconnected class snippets are a complete solution.
They are not.
We still need to connect them into one runnable system.
That requires answering several questions:
How does ParkingLot.park() claim a candidate safely?
Where do active tickets live?
What happens when the vehicle exits?
How do we stop the same ticket from being closed twice?
How does the complete design behave when two requests overlap?
And can all of this be expressed as Java code that actually runs rather than as fragments scattered through an article?
That is exactly what we will complete in LLD 01 — Design a Parking Lot, Part 2: Complete Java Implementation. ( will upload the content next )

Conversation
Comments
Sign in to join the conversation.