Technology · ssh / indianDevelopers
LLD 01 — Design a Parking Lot, Part 2: Complete Java Implementation
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.
In Part 1 ( link ), we deliberately stopped before pretending that a class diagram was a finished solution.
We established the model:
Vehicle
ParkingSpot
ParkingFloor
ParkingTicket
ParkingLotand separated two rules that can evolve independently:
SpotAllocationStrategy
PricingStrategyWe also identified the most important invariant in the system:One parking spot can belong to at most one active parking session at a time.Now we can turn those decisions into one complete Java implementation.
The Complete Java SolutionThe code below is intentionally framework-free.
There is no Spring Boot, database, REST API or dependency-injection framework hiding the object model.
It runs on Java 17+.
For easy copying, everything is contained inside a single top-level class. In a normal project, most of the nested types would usually live in their own files.
Create:
ParkingLotDemo.javaand place the following code inside it.
import java.time.Clock;
"F0-M1",
SpotType.MOTORCYCLE
),
new ParkingSpot(
"F0-C1",
SpotType.COMPACT
),
new ParkingSpot(
"F0-L1",
SpotType.LARGE
)
)
);
ParkingFloor firstFloor =
new ParkingFloor(
"F1",
1,
List.of(
new ParkingSpot(
"F1-C1",
SpotType.COMPACT
),
new ParkingSpot(
"F1-L1",
SpotType.LARGE
)
)
);
/*
* Fixed only so this demo
* produces deterministic output.
*
* A real application would normally
* use Clock.systemUTC().
*/
Clock clock =
Clock.fixed(
Instant.parse(
"2026-08-18T10:00:00Z"
),
ZoneOffset.UTC
);
ParkingLot parkingLot =
new ParkingLot(
List.of(
groundFloor,
firstFloor
),
new FirstAvailableSpotStrategy(),
new HourlyPricingStrategy(),
clock
);
Vehicle car =
new Vehicle(
"KA01AB1234",
VehicleType.CAR
);
ParkingTicket ticket =
parkingLot.park(car);
System.out.println(
"Allocated spot: "
+ ticket.spotId()
);
ParkingReceipt receipt =
parkingLot.exit(
ticket.id()
);
System.out.println(
"Fee: "
+ receipt.fee()
);
System.out.println(
"Released spot: "
+ receipt.spotId()
);
}
}Running the example produces:
Allocated spot: F0-C1
Fee: 40
Released spot: F0-C1The fixed clock exists only so that the example behaves deterministically.
In a real application we would normally construct the system using:
Clock.systemUTC()Injecting Clock rather than scattering Install.now() calls through the domain model also makes time-dependent behaviour much easier to test.
Follow One Car Through the System
Suppose this vehicle arrives:
KA01AB1234
VehicleType.CARThe runtime flow looks like this:

Imagine the allocation strategy returns:
F0-C1
F0-L1
F1-C1
F1-L1
The parking lot first attempts:
F0-C1.tryOccupy(vehicle);
If nobody else has claimed it, the operation succeeds.A ticket is created:
Vehicle: KA01AB1234
Spot: F0-C1
Entry time: 10:00and stored in activeTickets.
If another entrance request had taken F0-C1 after the candidate scan but before our claim, tryOccupy() would simply return false.
The parking lot would continue to F0-L1.
That is why:
candidate selection and state transition are deliberately separate concepts.
Why ParkingLot Is a Coordinator
Look at what ParkingLot.park() actually does:
ask strategy for candidates
↓
attempt to claim a spot
↓
create parking session
↓
store active ticketIt does not decide which vehicle categories fit inside which spots.
That belongs to ParkingSpot.
It does not decide how candidates are ordered.
That belongs to SpotAllocationStrategy.
It does not decide how an hour of parking is priced.
That belongs to PricingStrategy.
This prevents ParkingLot from gradually turning into a class containing every rule in the system.
For example, if the requirement changes to:
Always choose the spot closest to the entrance.
we can create:
final class NearestSpotStrategy
implements SpotAllocationStrategy {
// Order candidates by distance.
}Ticket creation does not change.
Pricing does not change.
Spot ownership does not change.
Only the policy that actually changed needs a new implementation.
The Strategy Pattern Has Earned Its Place
Design-pattern discussions become unhelpful when every interview problem is forced to contain five patterns.
Here, Strategy exists for a concrete reason.
There are at least two behaviours that can vary independently:
spot selection
pricingThat gives us:
SpotAllocationStrategy
├── FirstAvailableSpotStrategy
├── NearestSpotStrategy
└── ReservedCapacityStrategyand:
PricingStrategy
├── HourlyPricingStrategy
├── WeekendPricingStrategy
└── SlabPricingStrategyWe did not add a VehicleFactory merely because factories are common in LLD articles.
We did not turn ParkingLot into a Singleton.
We did not create repository interfaces before persistence entered the requirements.
Extensibility does not mean abstracting every line of code.
It means putting boundaries around behaviours that have a believable reason to change.
What Happens When Two Cars Enter Together?
Now we reach the concurrency detail that makes the problem much more realistic.
Imagine:
Entrance A
Entrance B
both execute the allocation strategy.
Both may receive:
F0-C1
as their first candidate.
That is acceptable.
The allocation strategy is giving us a candidate, not a reservation.
Both then execute:
spot.tryOccupy(vehicle);
The method is synchronized.
Only one thread can enter the critical section and transition:
AVAILABLE
↓
OCCUPIED
The other thread subsequently sees an existing parkedVehicle and receives:
false
It can move to another candidate.
The invariant survives.
This is much safer than:
if (spot.isAvailable()) {
spot.setVehicle(vehicle);
}
because that version separates the check from the mutation.
What Changes With Multiple Backend Servers?
Now suppose the parking facility grows.
Instead of one Java process handling every gate, we have:
Entry Server A
Entry Server B
Entry Server C
running in separate JVMs.
The synchronization inside ParkingSpot no longer solves the entire problem.
A lock inside Server A cannot stop Server B from modifying the same logical parking spot in a database.
But notice that the requirement itself has not changed:
One physical spot must have at most one active allocation.
Only the enforcement mechanism changes.
A real distributed implementation might enforce the state transition using a database transaction, conditional update, uniqueness constraint or another shared coordination mechanism.
This distinction is worth remembering:
LLD identifies the invariant and the object responsibilities. HLD determines how those invariants survive across processes and machines.
Exit Is More Than “Set Spot to Free”
When the vehicle leaves, several pieces of state change together.
Conceptually:
find active ticket
↓
calculate fee
↓
release spot
↓
close ticket
↓
remove active session
↓
return receiptOur in-memory version keeps those operations close together.
Once a database is introduced, however, this becomes a transaction question.
Imagine a persistent implementation does:
Ticket = CLOSEDand the process crashes before:
Spot = AVAILABLENow the database claims the vehicle has left while the spot remains occupied.
That is inconsistent state.
The design conversation has moved beyond objects into:
transaction boundaries
failure recovery
idempotency
concurrent updates
database constraintsThis is exactly why good LLD problems are useful backend preparation.
The object model eventually represents real state that has to remain correct when software fails.
Reservations Change the State Machine
Suppose reservations are added later.
Originally our spot has two useful states:
AVAILABLE
OCCUPIEDNow we need:

That probably introduces:
Reservation
reservation expiry
reservation ownershipBut the original allocation, ticket and pricing concepts do not have to be discarded.The model evolves.That is what we actually mean when we call a design extensible.
Other Changes Should Affect the Right Boundary
Suppose management wants floor displays showing available spots.
Repeatedly scanning every parking spot whenever a display refreshes may eventually become wasteful.
The floor might maintain counters or availability indexes.
Suppose pricing becomes:
first hour: 50
next three hours: 30/hour
after four hours: 20/hour
That changes the pricing strategy.
Suppose trucks should never consume the last two large spaces on the ground floor.
That changes allocation.
Suppose EV charging is introduced.
That changes the spot capability model.
The useful question is not:
Which design pattern can I add?
It is:
Which part of the system actually changed?
A clean LLD makes that answer reasonably obvious.
What to Keep in Mind During an Interview
There is no need to memorise a minute-by-minute interview script.
Real interviews rarely unfold that neatly.
Instead, keep asking a few questions as requirements arrive.
Where does this state belong?
Who should be allowed to mutate it?
Is this behaviour part of the entity, or is it a replaceable policy?
What invariant would be dangerous to violate?
Would the design remain correct if two requests execute together?
For this Parking Lot problem, the strongest invariant is simple:
A parking spot can belong to at most one active parking session at a time.
Once that statement is clear, many design choices become easier to defend.
The Final Mental Model
Our completed design is:
ParkingLot
│
├── ParkingFloor
│ └── ParkingSpot
│ └── parked Vehicle
│
├── SpotAllocationStrategy
│ └── FirstAvailableSpotStrategy
│
├── PricingStrategy
│ └── HourlyPricingStrategy
│
└── Active ParkingTickets
│
▼
ParkingReceipt
The responsibilities are equally clear.
ParkingLot coordinates the use case.
ParkingFloor groups physical capacity.
ParkingSpot protects occupancy.
Vehicle describes what is entering.
ParkingTicket represents an active session.
ParkingReceipt represents the completed session result.
SpotAllocationStrategy determines candidate ordering.
PricingStrategy calculates the fee.
And the complete implementation still leaves room for the system to evolve without placing every new requirement into one enormous class.
That is the real lesson behind the Parking Lot problem.
The interesting part is not drawing a box labelled ParkingLot.
It is deciding which pieces of state must remain correct, who owns them, and how the design reacts when the next requirement arrives.

Conversation
Comments
Sign in to join the conversation.