The goal is to design a robust Parking Lot system. Consider the following playbook:
- Clarify requirements (2-3 min) — ask questions
- Identify core use-cases (1 min) — list them
- Design the class diagram (5-7 min) — on whiteboard/paper
- Walk through code (15-20 min) — write clean OOP
- Discuss trade-offs (5 min) — patterns, concurrency, extensibility
Important
Never start coding without clarifying requirements. It shows technical maturity.
| # | Question to Ask | Why It Matters |
|---|---|---|
| 1 | How many floors / levels? | Decides if you need a ParkingFloor class |
| 2 | What types of vehicles? | Determines Vehicle hierarchy |
| 3 | What types of parking spots? | Determines ParkingSpot hierarchy |
| 4 | Multiple entrances/exits? | Need Entrance / Exit classes |
| 5 | How is payment handled? | Cash, card, UPI → Payment Strategy |
| 6 | Is there a display board? | Need DisplayBoard for availability |
| 7 | Hourly rate or flat rate? | Decides ParkingRate logic |
| 8 | Is there an admin panel? | Need Admin actor class |
- Customer enters → gets a ticket → car is assigned a spot
- Customer exits → pays fee → spot is freed
- Lot full → new car is denied entry
- Display board → shows live availability by spot type
- Admin → can add/remove spots, entrances, boards
classDiagram
direction TB
class ParkingLot {
-instance : ParkingLot$
+rate : ParkingRate
-spots : Map~int, ParkingSpot~
-tickets : Map~int, ParkingTicket~
-boards : List~DisplayBoard~
+getInstance()$ ParkingLot
+parkVehicle(v) ParkingTicket
+freeSlot(id) void
-canFit(v, s) boolean
}
class ParkingSpot {
<<abstract>>
#id : int
#isFree : boolean
#vehicle : Vehicle
+assignVehicle(v)* boolean
+removeVehicle() boolean
}
class Compact
class Large
class Accessible
class MotorcycleSpot
ParkingSpot <|-- Compact
ParkingSpot <|-- Large
ParkingSpot <|-- Accessible
ParkingSpot <|-- MotorcycleSpot
class Vehicle {
<<abstract>>
-licenseNo : String
-ticket : ParkingTicket
}
class Car
class Truck
class Van
class Motorcycle
Vehicle <|-- Car
Vehicle <|-- Truck
Vehicle <|-- Van
Vehicle <|-- Motorcycle
class ParkingTicket {
-ticketNo : int
-slotNo : int
-entryTime : Date
-exitTime : Date
-amount : double
-status : TicketStatus
}
class Payment {
<<abstract>>
#amount : double
#status : PaymentStatus
+initiateTransaction()* boolean
}
class Cash
class CreditCard
Payment <|-- Cash
Payment <|-- CreditCard
class ParkingRate {
+calculate(hrs, v, s) double
}
class DisplayBoard {
+update(spots) void
+showFreeSlot() void
}
class Entrance {
+getTicket(v) ParkingTicket
}
class Exit {
+validateTicket(t) void
}
ParkingLot --> ParkingSpot : contains
ParkingLot --> ParkingTicket : issues
ParkingLot --> DisplayBoard : has
ParkingLot --> ParkingRate : uses
Entrance --> ParkingLot : calls
Exit --> ParkingLot : calls
ParkingTicket --> Vehicle : for
ParkingSpot --> Vehicle : holds
Exit --> Payment : creates
Why? There is only ONE parking lot in the system. Every entrance, exit, and display board must refer to the same instance.
// Private constructor — nobody can do "new ParkingLot()"
private ParkingLot() {}
private static ParkingLot instance = null;
// Global access point — thread-unsafe version (discuss tradeoffs)
public static ParkingLot getInstance() {
if (instance == null) instance = new ParkingLot();
return instance;
}Tip
Follow-up: "Is this thread-safe?" Answer: No. For thread safety, use double-checked locking or an enum singleton.
public static synchronized ParkingLot getInstance() { ... }
// OR better — double-checked locking:
public static ParkingLot getInstance() {
if (instance == null) {
synchronized (ParkingLot.class) {
if (instance == null) instance = new ParkingLot();
}
}
return instance;
}Why? Different spot types have different allocation logic. Different vehicle types need to match compatible spots.
ParkingSpot (abstract)
├── Compact → fits Car
├── Large → fits Truck, Van
├── Accessible → fits Car (handicapped)
└── MotorcycleSpot → fits Motorcycle
Vehicle (abstract)
├── Car
├── Truck
├── Van
└── Motorcycle
The assignVehicle() method is declared abstract in ParkingSpot and each subclass implements it:
// In Compact.java
public boolean assignVehicle(Vehicle v) {
if (isFree) {
this.vehicle = v; isFree = false; return true;
}
return false;
}Why? Payment method can vary (cash, credit card, UPI). Each is a strategy.
public abstract class Payment {
protected double amount;
protected PaymentStatus status;
public abstract boolean initiateTransaction(); // strategy method
}
public class Cash extends Payment { ... }
public class CreditCard extends Payment { ... }In Exit.java, the strategy is chosen at runtime:
Payment p = (fee > 10) ? new CreditCard(fee) : new Cash(fee);
p.initiateTransaction();Three enums capture the lifecycle states:
| Enum | Values | Used In |
|---|---|---|
TicketStatus |
ISSUED, IN_USE, PAID, VALIDATED, CANCELED, REFUNDED | ParkingTicket |
PaymentStatus |
COMPLETED, FAILED, PENDING, UNPAID, REFUNDED | Payment |
AccountStatus |
ACTIVE, CLOSED, CANCELED, BLOCKLISTED, NONE | Account |
| Responsibility | How |
|---|---|
| Holds all spots | Map<Integer, ParkingSpot> spots |
| Holds all tickets | Map<Integer, ParkingTicket> tickets |
| Parks a vehicle | parkVehicle() — finds a free compatible spot |
| Frees a spot | freeSlot() — marks spot as available |
| Decides compatibility | canFit() — matches vehicle type to spot type |
The canFit() method is the BRAIN of the system:
private boolean canFit(Vehicle v, ParkingSpot s) {
if (v instanceof Motorcycle && s instanceof MotorcycleSpot) return true;
if ((v instanceof Truck || v instanceof Van) && s instanceof Large) return true;
if (v instanceof Car && (s instanceof Compact || s instanceof Accessible)) return true;
return false;
}Warning
instanceof chains are a code smell. A common follow-up question is: "How would you improve this?"
Answer: Use a mapping table or the Visitor pattern. For example:
Map<Class<? extends Vehicle>, Set<Class<? extends ParkingSpot>>> fitMap;- Has an
id,isFreeflag, and a reference to theVehicleparked in it assignVehicle()is abstract — each subclass prints its typeremoveVehicle()resets the spot (shared logic in the parent)
- Holds
licenseNoand a reference to itsParkingTicket - Subclasses (
Car,Truck,Van,Motorcycle) are marker classes — they exist purely for type-checking incanFit()
- Auto-incrementing ID via
static int ticketSeed = 1000 - Records:
slotNo,vehicle,entryTime,exitTime,amount,status - Created when a vehicle parks, updated when it exits
public double calculate(double hours, Vehicle v, ParkingSpot s) {
int hrs = (int)Math.ceil(hours);
double fee = 0;
if (hrs >= 1) fee += 4; // 1st hour: $4
if (hrs >= 2) fee += 3.5; // 2nd hour: $3.50
if (hrs >= 3) fee += 3.5; // 3rd hour: $3.50
if (hrs > 3) fee += (hrs - 3) * 2.5; // beyond 3h: $2.50/hr
return fee;
}Note
The Vehicle v and ParkingSpot s parameters are unused here, but they exist so you can later add different rates per vehicle type or spot type (extensibility point).
public ParkingTicket getTicket(Vehicle v) {
return ParkingLot.getInstance().parkVehicle(v);
}Very thin — delegates everything to ParkingLot. In a real system, this would also validate capacity.
This is where the complete exit flow happens:
- Set exit time on ticket
- Calculate fee using
ParkingRate - Create a
Payment(Cash or CreditCard) - Process payment
- Free the parking slot
- Mark ticket as PAID
public void update(Collection<ParkingSpot> spots) {
freeCount.clear();
for (ParkingSpot s : spots) {
if (s.isFree()) {
String type = s.getClass().getSimpleName();
freeCount.put(type, freeCount.getOrDefault(type, 0) + 1);
}
}
}Uses getClass().getSimpleName() to group by spot type — clever but fragile. In production, use an enum.
Accountholds credentials (userName,password,Person,AccountStatus)Adminextends it with abilities:addParkingSpot(),addDisplayBoard(),addEntrance(),addExit()- Currently stubs (
return true) — defines the interface
Simple data holders. These represent real-world entities.
sequenceDiagram
participant D as Driver (main)
participant E as Entrance
participant PL as ParkingLot
participant PS as ParkingSpot
participant T as ParkingTicket
D->>E: getTicket(car)
E->>PL: parkVehicle(car)
PL->>PL: iterate spots, call canFit()
PL->>PS: assignVehicle(car)
PS-->>PL: slot assigned, isFree=false
PL->>T: new ParkingTicket(slotNo, car)
T->>T: ticketNo = ticketSeed++, entryTime = now
T-->>PL: ticket created
PL-->>E: return ticket
E-->>D: return ticket
sequenceDiagram
participant D as Driver (main)
participant X as Exit
participant PL as ParkingLot
participant PR as ParkingRate
participant P as Payment
participant PS as ParkingSpot
D->>X: validateTicket(ticket)
X->>X: exitTime = now
X->>PR: calculate(hours, vehicle, spot)
PR-->>X: fee = $X.XX
X->>X: ticket.setAmount(fee)
X->>P: new Cash/CreditCard(fee)
X->>P: initiateTransaction()
P-->>X: payment complete
X->>PL: freeSlot(slotNo)
PL->>PS: removeVehicle()
PS-->>PL: slot freed, isFree=true
X->>X: ticket.setStatus(PAID)
Your Driver.java covers 3 key scenarios:
| Scenario | What Happens | Tests What |
|---|---|---|
| 1. Park a car | Car enters → assigned Accessible slot 1 → ticket issued | parkVehicle(), canFit(), ticket creation |
| 2. Exit & pay | Car exits after 1.5s → fee calculated → cash/card payment → slot freed | Exit.validateTicket(), ParkingRate, payment processing |
| 3. Lot full | Van, Motorcycle, Truck, Car enter → lot fills → next car denied | Capacity handling, "parking lot is full" message |
& Answers
Add a ParkingFloor class:
class ParkingFloor {
int floorId;
Map<Integer, ParkingSpot> spots;
DisplayBoard board;
}
// ParkingLot now has: List<ParkingFloor> floors;- Make
parkVehicle()synchronized or useReentrantLock - Use
ConcurrentHashMapinstead ofHashMapfor spots/tickets - Use double-checked locking for the Singleton
Replace instanceof with a configuration map:
private static final Map<Class<? extends Vehicle>, List<Class<? extends ParkingSpot>>> FIT_MAP = Map.of(
Car.class, List.of(Compact.class, Accessible.class),
Truck.class, List.of(Large.class),
Van.class, List.of(Large.class),
Motorcycle.class, List.of(MotorcycleSpot.class)
);class ElectricSpot extends ParkingSpot { ... }
class ElectricVehicle extends Vehicle { ... }
// Add mapping in canFit() or FIT_MAPModify ParkingRate.calculate() to use the Vehicle parameter:
double baseRate = (v instanceof Truck) ? 6.0 : 4.0;ParkingSpot→parking_spotstable (id, type, floor, is_free)ParkingTicket→ticketstable (ticket_no, slot_no, entry_time, exit_time, amount, status)Payment→paymentstable (id, ticket_id, amount, method, status)
| File | Role | Pattern | Lines |
|---|---|---|---|
| ParkingLot.java | Central manager | Singleton | 46 |
| ParkingSpot.java | Abstract spot | Inheritance | 19 |
| Compact.java | Compact spot | Polymorphism | 11 |
| Large.java | Large spot | Polymorphism | 11 |
| Accessible.java | Accessible spot | Polymorphism | 11 |
| MotorcycleSpot.java | Motorcycle spot | Polymorphism | 11 |
| Vehicle.java | Abstract vehicle | Inheritance | 9 |
| Car.java | Car type | Marker class | 2 |
| Truck.java | Truck type | Marker class | 2 |
| Van.java | Van type | Marker class | 2 |
| Motorcycle.java | Motorcycle type | Marker class | 2 |
| ParkingTicket.java | Ticket/receipt | Auto-increment ID | 34 |
| ParkingRate.java | Fee calculator | Tiered pricing | 12 |
| Payment.java | Abstract payment | Strategy | 11 |
| Cash.java | Cash payment | Strategy impl | 9 |
| CreditCard.java | Card payment | Strategy impl | 9 |
| Entrance.java | Entry gate | Delegate to Singleton | 8 |
| Exit.java | Exit gate | Orchestrates exit flow | 20 |
| DisplayBoard.java | Availability board | Observer-like | 25 |
| Admin.java | Admin actor | Extends Account | 8 |
| Account.java | Abstract account | Inheritance | 8 |
| Person.java | Person data | Value object | 7 |
| Address.java | Address data | Value object | 8 |
| TicketStatus.java | Ticket states | Enum | 2 |
| PaymentStatus.java | Payment states | Enum | 2 |
| AccountStatus.java | Account states | Enum | 2 |
| Driver.java | Demo/main | Test harness | 76 |
Think of it as 5 layers:
Layer 1: ENUMS → TicketStatus, PaymentStatus, AccountStatus
Layer 2: VALUE OBJECTS → Person, Address
Layer 3: ENTITIES → Vehicle (Car/Truck/Van/Motorcycle), ParkingSpot (Compact/Large/Accessible/MotorcycleSpot)
Layer 4: CORE LOGIC → ParkingLot (Singleton), ParkingTicket, ParkingRate, Payment (Cash/CreditCard)
Layer 5: INFRASTRUCTURE → Entrance, Exit, DisplayBoard, Account, Admin
Mnemonic: "EVEC-I" = Enums → Values → Entities → Core → Infrastructure