Designing a Parking Lot is one of the most common Object-Oriented Design (OOD) questions asked during software engineering interviews. It evaluates a candidate's ability to take a complex physical system and break it down into clean, maintainable software abstractions. Doing this successfully requires applying core object-oriented programming (OOP) principles: Inheritance, Encapsulation, and Polymorphism.
A functional parking lot system needs to model several entities:
- Vehicles: Supporting distinct types like Bikes, Cars, and Trucks.
- Parking Spots: Matching different vehicle sizes and enforcing size constraints (e.g., a Truck cannot fit into a Bike spot).
- Parking Coordinator: A manager class that allocates available spots to incoming vehicles and frees them when vehicles depart.
To visualize this design, imagine you manage a mailroom that receives packages of three sizes: letters (bikes), shoe boxes (cars), and large shipping crates (trucks).
To store these, you have a set of shelves divided into compartments of matching sizes: small slots, medium slots, and large racks. When a package arrives, you look at its size and search for the first empty slot that matches. You cannot put a large shipping crate into a letter slot. Likewise, you avoid placing a letter on a large rack because that wastes space. In this analogy, the packages represent Vehicle objects, the compartments are ParkingSpot objects, and you—the mailroom manager—act as the ParkingLot controller that coordinates the assignments.
Design Strategy
We structure our Java solution around three fundamental OOD strategies:
- Polymorphic Hierarchy: We define an abstract
Vehiclebase class. Concrete vehicle subclasses (likeCarorBike) inherit from it and specify their default type. This keeps the design open for extension. - Encapsulation of State: Each
ParkingSpotmanages its own state (isFree,parkedVehicle). It exposes apark(Vehicle)method that evaluates its own validation rules internally rather than letting external classes modify its fields. - Delegation of Responsibility: The
ParkingLotcoordinator class contains a collection of spots. When a vehicle arrives, the coordinator iterates through the spots, delegating the parking decision to each spot until one successfully accepts the vehicle.
Step-by-Step Execution Walkthrough
Let's trace this flow with a parking lot containing a single empty CAR spot:
- Vehicle Arrival: A
Carobject (license"MH12-1234") arrives. We calllot.parkVehicle(car). - Constraint Check: The loop inspects the spots. It invokes
spot1.park(car). The spot checks if it is free and if its spot type (CAR) matches the vehicle type (CAR). Since both conditions are true, the spot is marked occupied, and the operation returns success. - Subsequent Request: A second car arrives. We call
lot.parkVehicle(car2). The coordinator checksspot1, butspot1.park(car2)returns failure because the spot is already occupied. - Vehicle Departure: The first car leaves. We call
lot.unparkVehicle(car). The coordinator searches the spots, finds the spot holding the car, and callsspot1.leave(), resetting the spot's state to free.
Key Code Explanations
Here is why the main logic in the solution is important:
public abstract static class Vehicle: Enforces inheritance. By declaring this abstract, we ensure no generic "Vehicle" can be instantiated directly; it must be a concrete type like Car or Bike.if (isFree && v.type == type): Enforces type-safety. Prevents a Truck from parking in a Motorcycle spot.s.park(v): The delegation step. The lot itself doesn't modify the spot's attributes directly; it requests the spot to execute the action, respecting encapsulation.
Java Implementation Code
Below is the complete, self-contained Java source code that solves this problem. It also includes a main method that traces the execution with console outputs.
package io.practise.dsa;
import java.util.*;
public class DesignParkingLot {
// Enum representing supported vehicle/spot sizes
public enum VehicleType {
BIKE,
CAR,
TRUCK
}
// Abstract base class representing a generic vehicle
public abstract static class Vehicle {
VehicleType type;
String license;
public Vehicle(VehicleType type, String license) {
this.type = type;
this.license = license;
}
}
// Concrete Car class inheriting from Vehicle
public static class Car extends Vehicle {
public Car(String license) {
super(VehicleType.CAR, license);
}
}
// Class representing a single parking spot
public static class ParkingSpot {
private final VehicleType type;
private boolean isFree;
private Vehicle vehicle;
public ParkingSpot(VehicleType type) {
this.type = type;
this.isFree = true;
}
// Park the vehicle if spot is free and type matches
public boolean park(Vehicle v) {
if (isFree && v.type == this.type) {
this.vehicle = v;
this.isFree = false;
return true;
}
return false;
}
// Vacate the spot
public void leave() {
this.vehicle = null;
this.isFree = true;
}
public boolean isFree() {
return isFree;
}
public Vehicle getVehicle() {
return vehicle;
}
}
// Controller class managing multiple spots
public static class ParkingLot {
private final List<ParkingSpot> spots;
public ParkingLot(List<ParkingSpot> spots) {
this.spots = spots;
}
// Scan and park in the first available slot
public boolean parkVehicle(Vehicle v) {
for (ParkingSpot s : spots) {
if (s.park(v)) {
return true;
}
}
return false;
}
// Scan and vacate the spot holding the vehicle
public void unparkVehicle(Vehicle v) {
for (ParkingSpot s : spots) {
if (s.getVehicle() == v) {
s.leave();
break;
}
}
}
}
public static void main(String[] args) {
System.out.println("--- Design Parking Lot Demonstration ---");
ParkingSpot spot1 = new ParkingSpot(VehicleType.CAR);
ParkingLot lot = new ParkingLot(List.of(spot1));
Car car = new Car("MH12-1234");
System.out.println("Attempting to park Car [MH12-1234]...");
boolean park1 = lot.parkVehicle(car);
System.out.println("Parking Success: " + park1 + " (Expected: true)");
System.out.println("\nAttempting to park same Car again (no empty spots)...");
boolean park2 = lot.parkVehicle(car);
System.out.println("Parking Success: " + park2 + " (Expected: false)");
System.out.println("\nCar vacating spot...");
lot.unparkVehicle(car);
System.out.println("Spot status isFree: " + spot1.isFree() + " (Expected: true)");
}
}
Conclusion & Practical Takeaways
Distributing system logic into distinct, specialized classes (Vehicle subclasses, ParkingSpot verifiers, and ParkingLot coordinators) makes the codebase highly modular. This encapsulation ensures that if we decide to add new vehicle types, such as Electric Vehicles with charging stations, we can extend the system with minimal modifications to existing code.