Implement LLD for Parking Lot: Code Walkthrough
Theory is good, code is better. We implement the Parking Lot system in Java, focusing on the Singleton, Factory, and Strategy patterns.
Abstract AlgorithmsTLDR: This is the code companion to the Parking Lot System Design post. We implement the core classes (
ParkingLot,ParkingSpot,Ticket) in Java, apply the Singleton, Factory, and Strategy patterns, and use a Min-Heap to find the nearest available spot in O(log n).
๐ Design-to-Code: What We Are Building
In the design post we identified key requirements:
- Support multiple vehicle types:
CAR,TRUCK,BIKE. - Multiple floors, each with typed spots.
- A
Ticketissued on entry; freed on exit with fee calculated. - Find the nearest available spot for the vehicle's type.
- Thread-safe: multiple entry gates run concurrently.
Here we translate each requirement into Java classes with production-grade patterns.
๐ข The Domain Model: Vehicles, Spots, and Tickets
Vehicle Hierarchy
public enum VehicleType { BIKE, CAR, TRUCK }
public abstract class Vehicle {
private final String licensePlate;
private final VehicleType type;
protected Vehicle(String licensePlate, VehicleType type) {
this.licensePlate = licensePlate;
this.type = type;
}
public VehicleType getType() { return type; }
public String getLicensePlate() { return licensePlate; }
}
public class Car extends Vehicle { public Car(String plate) { super(plate, VehicleType.CAR); } }
public class Truck extends Vehicle { public Truck(String plate) { super(plate, VehicleType.TRUCK); } }
public class Bike extends Vehicle { public Bike(String plate) { super(plate, VehicleType.BIKE); } }
Parking Spot Hierarchy
public abstract class ParkingSpot {
private final int id;
private volatile boolean isFree = true;
private final VehicleType type;
private Vehicle parkedVehicle;
protected ParkingSpot(int id, VehicleType type) {
this.id = id;
this.type = type;
}
public synchronized boolean assign(Vehicle v) {
if (!isFree) return false;
this.parkedVehicle = v;
this.isFree = false;
return true;
}
public synchronized void free() {
this.parkedVehicle = null;
this.isFree = true;
}
public boolean isFree() { return isFree; }
public int getId() { return id; }
public VehicleType getType() { return type; }
}
public class BikeSpot extends ParkingSpot { public BikeSpot(int id) { super(id, VehicleType.BIKE); } }
public class CompactSpot extends ParkingSpot { public CompactSpot(int id) { super(id, VehicleType.CAR); } }
public class LargeSpot extends ParkingSpot { public LargeSpot(int id) { super(id, VehicleType.TRUCK); } }
โ๏ธ ParkingFloor: Managing Spots via a Min-Heap
Each floor maintains a Min-Heap per vehicle type. The heap is ordered by spot ID โ the smallest ID = nearest to the entrance.
Why Min-Heap?
findNearest()โO(1)peek,O(log n)remove.freeSpot()โO(log n)re-insert.- Outperforms linear scan
O(n)at scale.
import java.util.PriorityQueue;
import java.util.Map;
import java.util.HashMap;
public class ParkingFloor {
private final int floorNumber;
private final Map<VehicleType, PriorityQueue<ParkingSpot>> availableSpots;
public ParkingFloor(int floorNumber) {
this.floorNumber = floorNumber;
availableSpots = new HashMap<>();
for (VehicleType type : VehicleType.values()) {
availableSpots.put(type,
new PriorityQueue<>((a, b) -> a.getId() - b.getId()));
}
}
public synchronized ParkingSpot findAndAssign(Vehicle v) {
PriorityQueue<ParkingSpot> heap = availableSpots.get(v.getType());
if (heap.isEmpty()) return null;
ParkingSpot spot = heap.poll(); // O(log n)
spot.assign(v);
return spot;
}
public synchronized void releaseSpot(ParkingSpot spot) {
spot.free();
availableSpots.get(spot.getType()).offer(spot); // O(log n)
}
public void addSpot(ParkingSpot spot) {
availableSpots.get(spot.getType()).offer(spot);
}
}
flowchart TD
Vehicle["Vehicle.getType() = CAR"] --> Floor["ParkingFloor.findAndAssign()"]
Floor --> Heap["MinHeap[CompactSpot]\npeek โ Spot #3"]
Heap -->|poll O(log n)| Spot["Spot #3: assign(vehicle)"]
Spot --> Ticket["Issue Ticket(spotId=3, entryTime=now)"]
๐ง ParkingLot: Singleton + Multi-Floor Search
import java.time.Instant;
import java.util.List;
import java.util.ArrayList;
public class ParkingLot {
private static volatile ParkingLot instance;
private final List<ParkingFloor> floors = new ArrayList<>();
private ParkingLot() {}
public static ParkingLot getInstance() {
if (instance == null) {
synchronized (ParkingLot.class) {
if (instance == null) { // double-checked locking
instance = new ParkingLot();
}
}
}
return instance;
}
public void addFloor(ParkingFloor floor) {
floors.add(floor);
}
public Ticket park(Vehicle v) {
for (ParkingFloor floor : floors) {
ParkingSpot spot = floor.findAndAssign(v);
if (spot != null) {
return new Ticket(v, spot, floor, Instant.now());
}
}
throw new RuntimeException("Parking lot full for type: " + v.getType());
}
public double exit(Ticket ticket) {
Instant exitTime = Instant.now();
ticket.getFloor().releaseSpot(ticket.getSpot());
long minutes = (exitTime.getEpochSecond() - ticket.getEntryTime().getEpochSecond()) / 60;
return FeeCalculator.calculate(ticket.getVehicle().getType(), minutes);
}
}
Double-checked locking: The volatile keyword on instance prevents instruction reordering. Both null checks prevent creating two instances under race conditions at startup.
โ๏ธ Ticket and Fee Strategy Pattern
public class Ticket {
private final Vehicle vehicle;
private final ParkingSpot spot;
private final ParkingFloor floor;
private final Instant entryTime;
public Ticket(Vehicle vehicle, ParkingSpot spot, ParkingFloor floor, Instant entryTime) {
this.vehicle = vehicle;
this.spot = spot;
this.floor = floor;
this.entryTime = entryTime;
}
// Getters...
}
public class FeeCalculator {
private static final Map<VehicleType, Double> RATE_PER_HOUR = Map.of(
VehicleType.BIKE, 1.0,
VehicleType.CAR, 2.0,
VehicleType.TRUCK, 3.5
);
public static double calculate(VehicleType type, long minutes) {
double hours = Math.ceil(minutes / 60.0); // round up to next hour
return RATE_PER_HOUR.getOrDefault(type, 2.0) * hours;
}
}
Using a Strategy pattern here (even as simple as a rate map) makes it easy to swap pricing logic (dynamic pricing, peak hours) without touching ParkingLot or Ticket.
โ๏ธ Design Patterns Applied
| Pattern | Where Used | Why |
| Singleton | ParkingLot.getInstance() | Single source of truth for lot state |
| Min-Heap | ParkingFloor.availableSpots | O(log n) nearest-spot allocation |
| Strategy | FeeCalculator | Swap pricing logic independently |
| Factory Method | VehicleFactory.create(type, plate) | Decouple creation from usage |
| Template Method | ParkingSpot.assign() / free() | Common lifecycle in abstract base |
๐งช Testing the Critical Path
@Test
void carParksThenExits() {
ParkingLot lot = ParkingLot.getInstance();
ParkingFloor floor = new ParkingFloor(1);
floor.addSpot(new CompactSpot(1));
floor.addSpot(new CompactSpot(2));
lot.addFloor(floor);
Vehicle car = new Car("ABC-123");
Ticket ticket = lot.park(car);
assertEquals(1, ticket.getSpot().getId()); // nearest spot
assertTrue(!ticket.getSpot().isFree());
double fee = lot.exit(ticket);
assertTrue(ticket.getSpot().isFree());
assertTrue(fee >= 0);
}
๐ Summary
- Vehicle + Spot hierarchies model type safety โ
CompactSpotcan only fit aCAR. - Min-Heap per vehicle type per floor gives O(log n) nearest-spot allocation vs O(n) scan.
- Double-checked locking Singleton is thread-safe without synchronizing every
park()call. - FeeCalculator (Strategy) decouples pricing rules from lot management logic.
- Synchronized
assign()/free()onParkingSpotprevents double-booking under concurrent gate activity.
๐ Practice Quiz
Why use a Min-Heap for available spots instead of a simple list with a linear scan?
- A) Min-Heaps use less memory.
- B) Min-Heap gives O(log n) find-nearest vs O(n) scan, and O(log n) re-insert on exit.
- C) A list doesn't support multiple vehicle types.
Answer: B
What problem does double-checked locking solve in the
ParkingLotSingleton?- A) It prevents the JVM from garbage-collecting the instance.
- B) It ensures only one instance is created even when multiple threads enter
getInstance()simultaneously at startup. - C) It prevents two threads from parking in the same spot.
Answer: B
Where in this design would you apply Dependency Inversion to make fee calculation swappable without changing
ParkingLot?- A) Extract a
FeeStrategyinterface; inject it intoParkingLot's constructor.FeeCalculatorbecomes one implementation. - B) Move
FeeCalculatorinsideParkingSpot. - C) Store the fee rate table in the database.
Answer: A
- A) Extract a

Written by
Abstract Algorithms
@abstractalgorithms
More Posts
SFT for LLMs: A Practical Guide to Supervised Fine-Tuning
TLDR: Supervised fine-tuning (SFT) is the stage where a pretrained model learns task-specific response behavior from curated input-output examples. It is usually the first alignment step after pretraining and often the foundation for later RLHF. Good...
RLHF in Practice: From Human Preferences to Better LLM Policies
TLDR: Reinforcement Learning from Human Feedback (RLHF) helps align language models with human preferences after pretraining and SFT. The typical pipeline is: collect preference comparisons, train a reward model, then optimize a policy (often with KL...
PEFT, LoRA, and QLoRA: A Practical Guide to Efficient LLM Fine-Tuning
TLDR: Full fine-tuning updates every model weight, which is expensive in memory, compute, and storage. PEFT methods update only a small trainable slice. LoRA learns low-rank adapters on top of frozen base weights. QLoRA pushes efficiency further by q...

LLM Model Naming Conventions: How to Read Names and Why They Matter
TLDR: LLM names encode practical decisions: model family, size, training stage, context window, format, and quantization level. If you can decode naming conventions, you can avoid costly deployment mistakes and choose the right checkpoint faster. ๏ฟฝ...
