Model a multi-floor parking lot that assigns spots based on vehicle size and calculates fees using a pluggable pricing strategy.
Overview
A parking lot system manages multiple floors, each containing spots of varying sizes (SMALL, MEDIUM, LARGE). Vehicles (Motorcycle, Car, Truck) require a minimum spot size. The system issues a ParkingTicket on entry, records entry time, and on exit delegates fee calculation to a PricingStrategy. The ParkingLot itself is a Singleton so all floors share one access point. A Factory creates the correct spot type or vehicle subclass from an input string. This problem tests your ability to map real-world entities to a clean class hierarchy and apply at least two behavioral patterns under interview time pressure.
Requirements Analysis
Functional: assign a spot by vehicle size, issue a ticket on entry, release a spot and calculate fee on exit, support multiple floors. Non-functional: thread-safe spot assignment (synchronized or ConcurrentHashMap), extensible pricing (new rates without changing core logic), O(1) spot lookup per floor.
// No code here — just requirements
// Entities: ParkingLot, ParkingFloor, ParkingSpot, Vehicle, ParkingTicket, PaymentService
// Patterns : Singleton (ParkingLot), Factory (vehicle/spot), Strategy (pricing)Core Classes & Relationships
SpotType and VehicleType are enums. Vehicle is abstract with a getType() method; Car, Truck, Motorcycle extend it. ParkingSpot holds a SpotType and a nullable Vehicle reference. ParkingLot is a Singleton that owns a list of ParkingFloors. PricingStrategy is an interface with two implementations: FlatRatePricing and HourlyPricing.
public enum SpotType { SMALL, MEDIUM, LARGE }
public enum VehicleType { MOTORCYCLE, CAR, TRUCK }
public interface PricingStrategy {
double calculate(long entryEpoch, long exitEpoch);
}
public abstract class Vehicle {
protected final String licensePlate;
protected final VehicleType type;
Vehicle(String lp, VehicleType t) { this.licensePlate = lp; this.type = t; }
public VehicleType getType() { return type; }
}
public class Car extends Vehicle { Car(String lp) { super(lp, VehicleType.CAR); } }
public class Motorcycle extends Vehicle { Motorcycle(String lp) { super(lp, VehicleType.MOTORCYCLE); } }
public class Truck extends Vehicle { Truck(String lp) { super(lp, VehicleType.TRUCK); } }Java Implementation
ParkingSpot tracks occupancy. ParkingFloor iterates its spots to find the first available one that fits the vehicle. ParkingLot.assignSpot() delegates to each floor in order and returns a ParkingTicket. PaymentService.checkout() uses the injected PricingStrategy to compute the fee and frees the spot.
public class ParkingSpot {
private final String id;
private final SpotType type;
private Vehicle parkedVehicle;
public ParkingSpot(String id, SpotType type) { this.id = id; this.type = type; }
public boolean isAvailable() { return parkedVehicle == null; }
public void park(Vehicle v) { this.parkedVehicle = v; }
public void vacate() { this.parkedVehicle = null; }
public SpotType getType() { return type; }
public String getId() { return id; }
}
public class ParkingTicket {
private final String ticketId;
private final ParkingSpot spot;
private final long entryEpoch;
ParkingTicket(String ticketId, ParkingSpot spot) {
this.ticketId = ticketId;
this.spot = spot;
this.entryEpoch = System.currentTimeMillis();
}
public ParkingSpot getSpot() { return spot; }
public long getEntryEpoch() { return entryEpoch; }
}
public class ParkingFloor {
private final List<ParkingSpot> spots;
public ParkingFloor(List<ParkingSpot> spots) { this.spots = spots; }
public Optional<ParkingSpot> findSpot(VehicleType vehicleType) {
SpotType required = vehicleType == VehicleType.TRUCK ? SpotType.LARGE
: vehicleType == VehicleType.CAR ? SpotType.MEDIUM
: SpotType.SMALL;
return spots.stream()
.filter(s -> s.isAvailable() && s.getType() == required)
.findFirst();
}
}
public class ParkingLot { // Singleton
private static volatile ParkingLot instance;
private final List<ParkingFloor> floors;
private int ticketCounter = 0;
private ParkingLot(List<ParkingFloor> floors) { this.floors = floors; }
public static ParkingLot getInstance(List<ParkingFloor> floors) {
if (instance == null) synchronized (ParkingLot.class) {
if (instance == null) instance = new ParkingLot(floors);
}
return instance;
}
public ParkingTicket assignSpot(Vehicle vehicle) {
for (ParkingFloor floor : floors) {
Optional<ParkingSpot> spot = floor.findSpot(vehicle.getType());
if (spot.isPresent()) {
spot.get().park(vehicle);
return new ParkingTicket("TKT-" + (++ticketCounter), spot.get());
}
}
throw new IllegalStateException("No available spot for " + vehicle.getType());
}
}
public class PaymentService {
private final PricingStrategy strategy;
public PaymentService(PricingStrategy strategy) { this.strategy = strategy; }
public double checkout(ParkingTicket ticket) {
double fee = strategy.calculate(ticket.getEntryEpoch(), System.currentTimeMillis());
ticket.getSpot().vacate();
return fee;
}
}Key Points to Remember
- 1Use enums for SpotType and VehicleType to avoid stringly-typed comparisons and enable exhaustive switch expressions.
- 2Singleton ParkingLot with Double-Checked Locking + volatile ensures one global instance under concurrent access.
- 3Strategy pattern for pricing isolates fee logic — adding a WeekendSurgePricing requires zero changes to ParkingLot.
- 4Factory method (or a static VehicleFactory) decouples callers from concrete Car/Truck/Motorcycle constructors.
Interview Questions
Sign in to ask AriaHow would you handle concurrent spot assignment to prevent two threads from booking the same spot?
How would you extend the design to support reserved spots and monthly passes?
Why is a Singleton appropriate for ParkingLot but potentially problematic in a microservices architecture?
Ask Aria about Design a Parking Lot
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.