Parking Lot
Prefer to learn one decision at a time?
Try questions, reveal the reasoning, trace the code, and finish with the simulator.
Design a parking lot that accepts a vehicle, chooses a suitable free spot, issues a ticket, and frees the same spot when the vehicle leaves.

1. Requirements (~5 minutes)
The interview begins with a short prompt:
“Design a parking lot.”
That sentence sounds concrete because we have all used a parking lot. But it leaves the rules undecided. Can a motorcycle use a compact spot? Are there several floors? Who chooses the spot? Do we calculate a fee? If we answer those questions silently, we may build a correct solution to the wrong problem.
Start by turning the physical story into actions the program must perform. Do not name classes yet.
Use these four question groups every time
What must a user be able to do?
When does an action succeed, fail, or change state?
Which invalid actions must we reject?
What should we deliberately not build?
Ask these questions
AskWhich vehicle and spot types must the first version support?Show answerHide answer
Interviewer answer
Support motorcycles and cars. Spots are motorcycle, compact, or large. A motorcycle fits any spot; a car fits compact or large.
AskDoes the lot have several floors, and what does nearest mean?Show answerHide answer
Interviewer answer
Yes. Floor 0 is closest to the entrance, then floor 1, and so on. On one floor, prefer the smallest suitable spot type and then the lowest spot ID.
AskDoes the driver choose a spot, or does the system assign one?Show answerHide answer
Interviewer answer
The system assigns the nearest compatible free spot when a vehicle enters.
AskWhat identifies an active parking session, and how does exit work?Show answerHide answer
Interviewer answer
Entry returns a unique ticket containing the vehicle plate, floor, and spot ID. Exit supplies that ticket ID.
AskWhich invalid actions should be rejected?Show answerHide answer
Interviewer answer
Reject a vehicle that is already parked, an entry with no compatible spot, and an unknown exit ticket.
AskWill the assignment rule change?Show answerHide answer
Interviewer answer
Implement nearest compatible spot now, but keep the selection rule replaceable so another lot can use a different policy.
AskDo we need payment, reservations, electric charging, several entrances, or persistent storage?Show answerHide answer
Interviewer answer
No. Keep them as follow-up discussions. Assume calls reach one in-memory process sequentially.
Confirm the specification
Confirmed specification
7 requirements · 6 boundaries
The first version must
- 1.Support motorcycles and cars across motorcycle, compact, and large spots on multiple floors.
- 2.A motorcycle fits any spot; a car fits only a compact or large spot.
- 3.Assign the nearest compatible free spot: lower floor first, then smallest suitable type, then spot ID.
- 4.A successful entry occupies exactly one spot and returns a unique ticket containing the plate, floor, and spot ID.
- 5.Reject a duplicate vehicle or a vehicle with no compatible free spot without changing lot state.
- 6.A valid ticket releases its recorded spot; an unknown ticket changes nothing.
- 7.Keep the assignment rule replaceable, with nearest-compatible as the first strategy.
Not building in this version6 boundaries
- Payments and pricing
- Advance reservations
- Electric charging
- Several entrances acting concurrently
- Database or distributed consistency
- Pedestrian navigation and physical gates
2. Entities and relationships (~3 minutes)
Scan the confirmed requirements for candidate nouns: vehicle, parking lot, floor, spot, ticket, assignment rule, entrance gate, payment, and address. A noun from the real world is only a candidate. It becomes a class when it owns changing information or an important rule.
Final model
Types we will create
- Class
- Owns changing state or coordinates rules.
- Record
- A Java record for validated, immutable data with value equality.
- Interface
- Defines behavior that another implementation can replace.
Carries the plate and vehicle type together.
Owns compatibility, availability, occupation, and release.
Groups and locates spots on one numbered floor.
Coordinates entry, tickets, indexes, and exit.
Records the accepted vehicle-to-spot assignment.
Keeps the candidate ranking policy replaceable.
Returns the selected floor and spot together.
Returns a named status and optional ticket.
Enums
Enums name a small, fixed set of valid choices or outcomes.
Names the supported vehicle categories.
Names spot sizes and their compatibility rules.
Explains the result of an entry request.
Explains the result of an exit request.
Fields
Fields are simple values stored inside another type; they do not need independent behavior in this version.
Identifies a vehicle and prevents duplicate entry.
Orders floors during nearest-spot selection.
Identifies one spot within a floor.
Finds the accepted parking session during exit.
See an entry flow through the objects
Swipe to follow the flow →

Read the arrows from left to right. A Vehicle enters through ParkingLot.park. ParkingLot checks whether that plate already has an active ticket, then asks SpotAssignmentStrategy for a selection. The strategy reads the ordered ParkingFloor groups and their spots. The chosen ParkingSpot performs occupy(vehicle) because it owns the changing availability. Only after that succeeds does ParkingLot return a ParkingTicket. If no selection exists, it returns a rejection and no spot changes.
The flow teaches an important boundary: the strategy may choose a spot, but it does not occupy it or issue tickets. Selection policy and parking-session state are separate jobs.
3. Class design (10–15 minutes)
Bridge from the requirements using the same chain each time:
requirement → responsible object → state it remembers → method that changes or reads that state
Start with ParkingLot.park because that is the main action visible to the caller. Then follow its needs downward into the strategy, floor, and spot.
One class at a time
Design the state owners
Start with the coordinator, then follow the request into the classes that protect changing state. Select a class to see only the fields and behavior needed for its responsibility.
Class 1 of 3
ParkingLot
Coordinates entry and exit while keeping ticket and vehicle indexes consistent.
Requirement behind this class
Park a vehicle once, return a ticket, exit by ticket ID, and leave accepted state unchanged after rejection.
State it remembers
Private information required to perform this class's job.
Provides the ordered floors available to selection.
Delegates the replaceable ranking rule.
Finds the accepted session during exit.
Rejects duplicate vehicle entry.
Allocates deterministic IDs only for accepted parking.
Behavior it owns
Public use cases and internal rules kept beside that state.
Checks duplication, selects and occupies a spot, then records the ticket.
Finds the recorded spot, releases it, and removes both indexes.
Expose safe read-only views of current state.
Record success only after the chosen spot accepts occupation.
- Invariant it protects
- Every active ticket, plate index, and occupied spot describe the same accepted parking session.
- Works with
- ParkingFloor, ParkingSpot, SpotAssignmentStrategy, and immutable ticket/result records.
- Does not own
- Spot compatibility rules or candidate ranking details.
- Principle demonstrated
- Validation before mutation protects several related indexes as one workflow.
Supporting types
These types carry stable data or define replaceable behavior. They support the state owners without becoming another place to store live workflow state.
Carry validated request, selection, receipt, and result data.
They cross boundaries as stable values and never become a second route to mutate live parking state.
Defines read-only selection from floors and a Vehicle.
It may return SpotSelection but cannot occupy a spot or issue a ticket.
Filters compatible free spots and compares floor, fit, then spot ID.
The implementation contains ranking behavior only; ParkingLot retains the accepted-state workflow.
The explorer keeps the workflow compact: ParkingLot coordinates, ParkingFloor groups, and ParkingSpot protects occupation. With those boundaries clear, we can examine the one algorithm intentionally delegated outside the state owners.
Understand the allocation algorithm
The practical problem is not merely finding an empty spot. It is finding the first compatible spot according to a rule both interviewer and code understand.
Consider a car entering this lot:
- Floor 0 has
M-01free andL-01free. - Floor 1 has
C-11free. M-01is rejected because a car cannot fit there.L-01is compatible and is on floor 0.C-11is a tighter fit, but floor 1 is farther away.- The strategy chooses
L-01because floor number is the first comparison.
The implementation filters unusable spots, creates a SpotSelection containing the floor and spot, and sorts candidates by three keys:
floor number → fit preference → spot ID
If there are S spots, filtering costs O(S) and sorting costs O(S log S). The lesson uses sorting because the rule is easy to read in an interview. A large fixed lot could maintain ordered free-spot sets and make selection faster, but every occupation and release would then need to keep those indexes correct.
Decide whether a pattern helps
Use: Strategy for spot assignment
The exact behavior expected to vary is select(floors, vehicle). ParkingLot depends on the SpotAssignmentStrategy interface, and the current implementation is NearestAvailableSpotStrategy. If another lot later prefers the cheapest floor or reserves large spots, that rule changes in one implementation while duplicate checks, occupation, ticket creation, and exit remain untouched.
Do not force: Factory for vehicles and spots
Construction is simple and visible: each value needs an ID or plate and an enum type. A factory would hide straightforward constructors without resolving a current rule. Introduce one only if creation later needs several steps or configuration.
Do not force: Singleton for ParkingLot
Tests may create several independent lots, and a company may operate several locations. A normal object models that naturally. Global singleton state would make isolation harder and is not required by “one parking lot” in the prompt.
Connect the classes and review the final blueprint
ParkingLot has floors and one assignment Strategy. A ParkingFloor has ParkingSpots. None of these is a specialized form of another, so composition expresses the relationships better than inheritance.
- Single responsibility: ParkingLot coordinates sessions, Strategy ranks candidates, Floor groups spots, and Spot owns occupation.
- Encapsulation: callers cannot replace the spot lists or edit an occupant directly.
- Immutability: vehicles, selections, tickets, and results cross boundaries as stable values.
- Validation before mutation: duplicate and no-spot failures return before occupation, ticket creation, or index changes.

How to read it: Vehicle, ticket, selection, and result records carry completed decisions without exposing mutable parking state.
Design principle: Immutability makes accepted inputs and returned facts safe to share.
4. Implementation (~10 minutes)
The code below is a complete Java 17 program. Expected user failures return named results. Invalid construction, such as a blank spot ID, throws an exception because that is a programmer mistake rather than a normal parking outcome.
What the code does
It models compatible spots on several floors, selects the nearest suitable free spot, prevents duplicate entry, issues active tickets, and releases the recorded spot on exit.
Why it is structured this way
ParkingLot owns the workflow and ticket indexes, the strategy owns only selection, ParkingFloor owns its spot group, and ParkingSpot owns availability. Each rule stays beside the state it needs.
Follow one request through the code
- 1A caller sends ParkingLot.park with an immutable Vehicle.
- 2ParkingLot rejects an already-active plate before asking for a spot.
- 3NearestAvailableSpotStrategy filters and orders compatible free spots.
- 4The selected ParkingSpot validates and records the vehicle.
- 5ParkingLot creates a ticket and updates both active-ticket indexes.
Code concepts to understand
How Strategy is used hereParkingLot controls the workflow; the strategy answers only one question: which spot should be tried?
ParkingLot.park calls strategy.select(floors, vehicle). The strategy returns Optional.empty() when no candidate exists, or a SpotSelection containing the chosen floor and spot.
The strategy does not occupy the spot, create the ticket, or update active sessions. Those steps remain in ParkingLot, so replacing the selection rule cannot accidentally replace the rest of the parking workflow.
Why SpotSelection contains both floor and spotExit and ticket creation need the floor that owns the chosen spot.
Returning only ParkingSpot would force ParkingLot to scan every floor again to discover where it came from. SpotSelection is an immutable pair that keeps the result of the search together. It has value equality because it is a Java record.
Validation order protects stateEvery expected rejection happens before a ticket or spot is changed.
park checks duplicate plate, then asks for a selection, then occupies the selected spot, and only then creates ticket indexes. leave finds the active ticket before releasing anything. Therefore an unknown ticket and a full-lot rejection leave all existing sessions unchanged.
Why Optional is useful for a missing selectionNo suitable spot is an expected search result, not a broken program.
SpotAssignmentStrategy.select returns Optional<SpotSelection>. The caller must handle “nothing found” explicitly. Exceptions are reserved for impossible internal states or invalid object construction.
What this code does not guarantee about concurrencyThe current scope processes one request at a time inside one in-memory model.
Two entrances could both select the same free spot before either calls occupy. That is a race condition: the result depends on how the operations interleave. occupy prevents the second mutation inside one sequential call, but the complete select → occupy → ticket operation is not protected as one unit.
If concurrent entrances become a requirement, protect that critical section with a lock in one Java process and add a deterministic two-caller test. Across several application servers, use a shared transaction, unique database constraint, optimistic version, or another shared consistency mechanism. An in-process Java lock cannot protect another server.
Fixed value types
Read these first. They define the vocabulary used by the rest of the model. SpotType.canFit contains the compatibility table, so the same rule is not repeated inside every strategy.
public enum VehicleType {
MOTORCYCLE,
CAR
}public enum SpotType {
MOTORCYCLE,
COMPACT,
LARGE;
public boolean canFit(VehicleType vehicleType) {
return switch (vehicleType) {
case MOTORCYCLE -> true;
case CAR -> this == COMPACT || this == LARGE;
};
}
public int preferenceFor(VehicleType vehicleType) {
if (!canFit(vehicleType)) return Integer.MAX_VALUE;
return switch (this) {
case MOTORCYCLE -> 0;
case COMPACT -> vehicleType == VehicleType.CAR ? 0 : 1;
case LARGE -> vehicleType == VehicleType.CAR ? 1 : 2;
};
}
}public enum ParkingStatus {
PARKED,
ALREADY_PARKED,
NO_COMPATIBLE_SPOT
}public enum ExitStatus {
RELEASED,
TICKET_NOT_FOUND
}Immutable request and result values
Vehicle, SpotSelection, ParkingTicket, and ParkingResult are records. Read their compact constructors as boundary checks: a valid object can never contain a blank ID or a contradictory result such as PARKED without a ticket.
import java.util.Locale;
import java.util.Objects;
public record Vehicle(String licensePlate, VehicleType type) {
public Vehicle {
if (licensePlate == null || licensePlate.isBlank()) {
throw new IllegalArgumentException("license plate is required");
}
licensePlate = licensePlate.trim().toUpperCase(Locale.ROOT);
type = Objects.requireNonNull(type, "vehicle type is required");
}
}import java.util.Objects;
public record SpotSelection(ParkingFloor floor, ParkingSpot spot) {
public SpotSelection {
floor = Objects.requireNonNull(floor);
spot = Objects.requireNonNull(spot);
}
}import java.util.Objects;
public record ParkingTicket(
String id,
String licensePlate,
int floorNumber,
String spotId) {
public ParkingTicket {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("ticket id is required");
}
if (licensePlate == null || licensePlate.isBlank()) {
throw new IllegalArgumentException("license plate is required");
}
if (floorNumber < 0) {
throw new IllegalArgumentException("floor number cannot be negative");
}
if (spotId == null || spotId.isBlank()) {
throw new IllegalArgumentException("spot id is required");
}
}
}import java.util.Objects;
import java.util.Optional;
public record ParkingResult(ParkingStatus status, ParkingTicket ticket) {
public ParkingResult {
status = Objects.requireNonNull(status);
if (status == ParkingStatus.PARKED && ticket == null) {
throw new IllegalArgumentException("PARKED requires a ticket");
}
if (status != ParkingStatus.PARKED && ticket != null) {
throw new IllegalArgumentException("Rejected parking cannot have a ticket");
}
}
public static ParkingResult parked(ParkingTicket ticket) {
return new ParkingResult(ParkingStatus.PARKED, Objects.requireNonNull(ticket));
}
public static ParkingResult rejected(ParkingStatus status) {
if (status == ParkingStatus.PARKED) {
throw new IllegalArgumentException("Use parked() for success");
}
return new ParkingResult(status, null);
}
public Optional<ParkingTicket> optionalTicket() {
return Optional.ofNullable(ticket);
}
}State-owning objects
Read ParkingSpot.occupy before ParkingFloor. It shows the most important state rule: check compatibility and availability before storing the vehicle. ParkingFloor then provides a safe group and lookup without exposing a mutable list.
import java.util.Objects;
import java.util.Optional;
public final class ParkingSpot {
private final String id;
private final SpotType type;
private Vehicle occupiedBy;
public ParkingSpot(String id, SpotType type) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("spot id is required");
}
this.id = id;
this.type = Objects.requireNonNull(type);
}
public boolean canFit(Vehicle vehicle) {
return vehicle != null && type.canFit(vehicle.type());
}
public boolean occupy(Vehicle vehicle) {
if (occupiedBy != null || !canFit(vehicle)) return false;
occupiedBy = vehicle;
return true;
}
public boolean release() {
if (occupiedBy == null) return false;
occupiedBy = null;
return true;
}
public boolean isAvailable() {
return occupiedBy == null;
}
public String getId() {
return id;
}
public SpotType getType() {
return type;
}
public Optional<Vehicle> getVehicle() {
return Optional.ofNullable(occupiedBy);
}
}import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
public final class ParkingFloor {
private final int number;
private final List<ParkingSpot> spots;
public ParkingFloor(int number, List<ParkingSpot> spots) {
if (number < 0) {
throw new IllegalArgumentException("floor number cannot be negative");
}
Objects.requireNonNull(spots);
if (spots.isEmpty()) {
throw new IllegalArgumentException("a floor needs at least one spot");
}
Set<String> ids = new HashSet<>();
for (ParkingSpot spot : spots) {
Objects.requireNonNull(spot);
if (!ids.add(spot.getId())) {
throw new IllegalArgumentException("duplicate spot id on floor: " + spot.getId());
}
}
this.number = number;
this.spots = List.copyOf(spots);
}
public Optional<ParkingSpot> findSpot(String spotId) {
return spots.stream()
.filter(spot -> spot.getId().equals(spotId))
.findFirst();
}
public int availableSpotCount() {
return (int) spots.stream().filter(ParkingSpot::isAvailable).count();
}
public int getNumber() {
return number;
}
public List<ParkingSpot> getSpots() {
return spots;
}
}Assignment policy
The interface is intentionally small. The current strategy reads every spot, removes occupied or incompatible candidates, and applies the exact ordering agreed in Requirements. The final spot-ID comparison is the deterministic tie-breaker.
import java.util.List;
import java.util.Optional;
public interface SpotAssignmentStrategy {
Optional<SpotSelection> select(List<ParkingFloor> floors, Vehicle vehicle);
}import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
public final class NearestAvailableSpotStrategy implements SpotAssignmentStrategy {
@Override
public Optional<SpotSelection> select(List<ParkingFloor> floors, Vehicle vehicle) {
Objects.requireNonNull(floors);
Objects.requireNonNull(vehicle);
Comparator<SpotSelection> nearestFirst = Comparator
.comparingInt((SpotSelection selection) -> selection.floor().getNumber())
.thenComparingInt(selection ->
selection.spot().getType().preferenceFor(vehicle.type()))
.thenComparing(selection -> selection.spot().getId());
return floors.stream()
.flatMap(floor -> floor.getSpots().stream()
.filter(ParkingSpot::isAvailable)
.filter(spot -> spot.canFit(vehicle))
.map(spot -> new SpotSelection(floor, spot)))
.min(nearestFirst);
}
}Parking workflow and demo
Read ParkingLot.park from top to bottom. Its order mirrors the requirement discussion: reject duplicate, select, occupy, issue ticket, index the session. leave reverses only the state recorded by a known ticket. ParkingLotDemo exercises success and both important rejection paths.
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public final class ParkingLot {
private final List<ParkingFloor> floors;
private final SpotAssignmentStrategy strategy;
private final Map<String, ParkingTicket> activeTicketsById = new LinkedHashMap<>();
private final Map<String, String> activeTicketIdByPlate = new HashMap<>();
private int nextTicketNumber = 1;
public ParkingLot(List<ParkingFloor> floors, SpotAssignmentStrategy strategy) {
Objects.requireNonNull(floors);
if (floors.isEmpty()) {
throw new IllegalArgumentException("parking lot needs at least one floor");
}
validateUniqueFloorAndSpotIds(floors);
this.floors = List.copyOf(floors);
this.strategy = Objects.requireNonNull(strategy);
}
public ParkingResult park(Vehicle vehicle) {
Objects.requireNonNull(vehicle);
if (activeTicketIdByPlate.containsKey(vehicle.licensePlate())) {
return ParkingResult.rejected(ParkingStatus.ALREADY_PARKED);
}
SpotSelection selection = strategy.select(floors, vehicle).orElse(null);
if (selection == null) {
return ParkingResult.rejected(ParkingStatus.NO_COMPATIBLE_SPOT);
}
if (!selection.spot().occupy(vehicle)) {
throw new IllegalStateException("strategy returned an unusable spot");
}
String ticketId = String.format("T%03d", nextTicketNumber++);
ParkingTicket ticket = new ParkingTicket(
ticketId,
vehicle.licensePlate(),
selection.floor().getNumber(),
selection.spot().getId());
activeTicketsById.put(ticketId, ticket);
activeTicketIdByPlate.put(vehicle.licensePlate(), ticketId);
return ParkingResult.parked(ticket);
}
public ExitStatus leave(String ticketId) {
if (ticketId == null || ticketId.isBlank()) {
return ExitStatus.TICKET_NOT_FOUND;
}
ParkingTicket ticket = activeTicketsById.get(ticketId);
if (ticket == null) return ExitStatus.TICKET_NOT_FOUND;
ParkingSpot spot = floors.stream()
.filter(floor -> floor.getNumber() == ticket.floorNumber())
.findFirst()
.flatMap(floor -> floor.findSpot(ticket.spotId()))
.orElseThrow(() -> new IllegalStateException("ticket points to a missing spot"));
if (!spot.release()) {
throw new IllegalStateException("ticket points to an empty spot");
}
activeTicketsById.remove(ticketId);
activeTicketIdByPlate.remove(ticket.licensePlate());
return ExitStatus.RELEASED;
}
public List<ParkingTicket> getActiveTickets() {
return List.copyOf(activeTicketsById.values());
}
public List<ParkingFloor> getFloors() {
return floors;
}
private static void validateUniqueFloorAndSpotIds(List<ParkingFloor> floors) {
Set<Integer> floorNumbers = new HashSet<>();
Set<String> spotIds = new HashSet<>();
for (ParkingFloor floor : floors) {
Objects.requireNonNull(floor);
if (!floorNumbers.add(floor.getNumber())) {
throw new IllegalArgumentException("duplicate floor: " + floor.getNumber());
}
for (ParkingSpot spot : floor.getSpots()) {
if (!spotIds.add(spot.getId())) {
throw new IllegalArgumentException("duplicate spot id: " + spot.getId());
}
}
}
}
}import java.util.List;
public final class ParkingLotDemo {
public static void main(String[] args) {
ParkingFloor ground = new ParkingFloor(0, List.of(
new ParkingSpot("M-01", SpotType.MOTORCYCLE),
new ParkingSpot("C-01", SpotType.COMPACT),
new ParkingSpot("L-01", SpotType.LARGE)));
ParkingFloor first = new ParkingFloor(1, List.of(
new ParkingSpot("C-11", SpotType.COMPACT),
new ParkingSpot("L-11", SpotType.LARGE)));
ParkingLot lot = new ParkingLot(
List.of(ground, first),
new NearestAvailableSpotStrategy());
Vehicle carOne = new Vehicle("ka-01-ab-1001", VehicleType.CAR);
ParkingResult firstEntry = lot.park(carOne);
print(firstEntry);
ParkingResult duplicate = lot.park(carOne);
System.out.println("Duplicate: " + duplicate.status()
+ ", active tickets: " + lot.getActiveTickets().size());
print(lot.park(new Vehicle("KA-01-AB-1002", VehicleType.CAR)));
print(lot.park(new Vehicle("KA-01-AB-1003", VehicleType.CAR)));
print(lot.park(new Vehicle("KA-01-AB-1004", VehicleType.CAR)));
print(lot.park(new Vehicle("KA-01-AB-1005", VehicleType.CAR)));
String firstTicketId = firstEntry.ticket().id();
System.out.println("Exit " + firstTicketId + ": " + lot.leave(firstTicketId));
print(lot.park(new Vehicle("KA-01-AB-1005", VehicleType.CAR)));
System.out.println("Unknown exit: " + lot.leave("T999"));
}
private static void print(ParkingResult result) {
if (result.status() != ParkingStatus.PARKED) {
System.out.println("Entry: " + result.status());
return;
}
ParkingTicket ticket = result.ticket();
System.out.printf("%s -> %s on floor %d, spot %s%n",
result.status(), ticket.id(), ticket.floorNumber(), ticket.spotId());
}
}Verify the design with concrete state changes
- 1Initial: five spots are free. Floor 0 has motorcycle, compact, and large spots; floor 1 has compact and large spots.
- 2Car 1001 enters. The strategy rejects M-01, chooses floor 0 compact spot C-01, and ParkingLot returns T001.
- 3Car 1001 enters again. ParkingLot returns ALREADY_PARKED before selection; C-01 and the one active ticket remain unchanged.
- 4Four more cars are attempted. L-01, C-11, and L-11 are occupied; the fifth car receives NO_COMPATIBLE_SPOT while motorcycle spot M-01 remains free.
- 5leave(T001) finds C-01 from the ticket, releases it, and removes both ticket indexes.
- 6Car 1005 tries again and receives C-01. leave(T999) returns TICKET_NOT_FOUND and changes nothing.
Use the simulator to prove the same rules. Park the same plate twice, exit a ticket, and load the compatibility edge case. In that edge case, free spots exist, but a car is still rejected because neither free spot is compatible.
Try the allocation rule
Parking lot simulator
Available
Available
Available
Available
Available
Available
Selection order: lower floor → smallest suitable spot type → spot ID.
Entry gate
Active tickets
No vehicles are parked.
What the objects did
- Lot ready. Six spots are free across two floors.
5. Extensions (~5 minutes)
“Now support two entrances at the same time”
The changed requirement affects the select → occupy → create-ticket sequence owned by ParkingLot. Two threads must not both act on the same selection. In one Java process, protect that complete critical section with a lock and demonstrate two callers competing for one spot, where exactly one receives PARKED. A lock around only ParkingSpot.occupy prevents two occupants, but the losing request must retry selection if another compatible spot exists. Across servers, move the guarantee to shared transactional storage; a local lock is not enough.
“Prefer reserved or electric-charging spots for eligible vehicles”
Compatibility currently belongs to SpotType, and ranking belongs to SpotAssignmentStrategy. Add the new vehicle eligibility and spot capability, then update or replace the strategy. Do not place charging rules inside ticket creation.
“Calculate a fee at exit”
The ticket must now remember entry time, and exit needs an exit time. Add a PricingStrategy only when several pricing rules are required, such as hourly, weekend, or vehicle-based pricing. Use an injected clock in tests so fee examples remain deterministic.
“Show available counts on entrance boards”
Each floor already owns its spots, so it can calculate counts by spot type. For a small in-memory lot, calculating on request is simplest. If reads become frequent, add maintained counters beside occupy and release, then test that rejected actions do not change them.
“Let drivers reserve a spot before arrival”
A boolean occupied/free state is no longer enough. Introduce explicit spot states such as AVAILABLE, RESERVED, and OCCUPIED, plus a reservation ID and expiry rule. This is the point where a state-transition design becomes useful; do not add those states to the basic version.