It should efficiently allocate available parking spots, release them when vehicles exit, and keep track of occupied and available spaces.
The design should be extensible, allowing new vehicle types, parking spot types, and pricing strategies to be added with minimal changes.
Requirements
A correct parking lot implementation should meet the following requirements:1. Park a vehicle in a suitable parking spot.
2. Remove a vehicle when it exits.
3. Prevent parking when no suitable spot is available.
4. Support multiple vehicle and parking spot types.
5. Calculate parking charges when a vehicle exits.
The most common design uses inheritance for vehicle and parking spot types, while delegating parking allocation and pricing to dedicated service classes.
Design
A ParkingLot contains multiple ParkingSpot objects. Each ParkingSpot supports a specific VehicleType.A vehicle can only occupy a compatible parking spot. A ParkingService searches for an available spot and allocates it.
A Ticket stores the parking details and is used to calculate parking charges when the vehicle exits.
ParkingLot
+--------------------------------------+
| |
| ParkingSpot ParkingSpot |
| CAR MOTORCYCLE |
| |
| Ticket Ticket |
| |
+--------------------------------------+
Java Implementation
Vehicle represents a vehicle, ParkingSpot represents a parking space that supports a specific vehicle type, Ticket records the parking entry time and calculates the parking charge, and ParkingLot manages parking and vehicle exits.When a vehicle arrives, the parking lot searches for the first compatible empty parking spot, parks the vehicle, and issues a Ticket.
When the vehicle exits, the parking spot is released, and the ticket calculates the parking fee based on the total parking duration.
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
enum VehicleType {
CAR,
BIKE,
TRUCK
}
class Vehicle {
private final String number;
private final VehicleType type;
public Vehicle(String number, VehicleType type) {
this.number = number;
this.type = type;
}
// getters
}
class ParkingSpot {
private final int id;
private final VehicleType supportedType;
private Vehicle vehicle;
public ParkingSpot(int id, VehicleType supportedType) {
this.id = id;
this.supportedType = supportedType;
}
public boolean canPark(Vehicle vehicle) {
return this.vehicle == null
&& vehicle.getType() == supportedType;
}
public void park(Vehicle vehicle) {
this.vehicle = vehicle;
}
public void removeVehicle() {
vehicle = null;
}
public Vehicle getVehicle() {
return vehicle;
}
public int getId() {
return id;
}
}
class Ticket {
private final Vehicle vehicle;
private final ParkingSpot spot;
private final LocalDateTime entryTime;
public Ticket(Vehicle vehicle, ParkingSpot spot) {
this.vehicle = vehicle;
this.spot = spot;
this.entryTime = LocalDateTime.now();
}
public double calculateCharge() {
long hours = Math.max(
1,
Duration.between(
entryTime,
LocalDateTime.now()
).toHours()
);
return hours * 20;
}
public ParkingSpot getSpot() {
return spot;
}
}
class ParkingLot {
private final List<ParkingSpot> spots =
new ArrayList<>();
public void addSpot(ParkingSpot spot) {
spots.add(spot);
}
public Ticket park(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.canPark(vehicle)) {
spot.park(vehicle);
return new Ticket(vehicle, spot);
}
}
return null;
}
public double exit(Ticket ticket) {
ticket.getSpot().removeVehicle();
return ticket.calculateCharge();
}
}
This implementation separates the responsibilities of vehicle management, parking spot allocation, and ticket generation.
The parking lot simply allocates the first compatible free spot and releases it when the vehicle exits.
Example
The example creates a parking lot with separate parking spots for a CAR and a BIKE. A car enters the parking lot, is assigned a compatible parking spot, and receives a parking ticket.When the vehicle exits, the parking spot is released, the parking charge is calculated using the ticket, and the total amount is printed.
public class Main {
public static void main(String[] args) {
ParkingLot parkingLot = new ParkingLot();
parkingLot.addSpot(
new ParkingSpot(1, VehicleType.CAR)
);
parkingLot.addSpot(
new ParkingSpot(2, VehicleType.BIKE)
);
Vehicle vehicle = new Vehicle(
"UP32AB1234",
VehicleType.CAR
);
Ticket ticket = parkingLot.park(vehicle);
if (ticket != null) {
System.out.println("Vehicle Parked");
}
double amount = parkingLot.exit(ticket);
System.out.println(amount);
}
}
Output:
Vehicle Parked
20.0
Complexity
Parking requires searching for a compatible free parking spot. Exiting simply releases the allocated parking spot and calculates the parking fee.Time Complexity
Park Vehicle β O(n)
Exit Vehicle β O(1)
Space Complexity β O(number of parking spots)
Improving the Design
The current implementation scans every parking spot to find a suitable one. This works well for small parking lots but becomes inefficient as the number of parking spots grows.A better design groups available parking spots by VehicleType.
Instead of iterating through all parking spots, the parking lot directly retrieves the appropriate queue for the vehicle type and allocates the first available spot.
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
class ParkingLot {
private final Map<VehicleType, Queue<ParkingSpot>>
availableSpots = new HashMap<>();
public ParkingLot() {
for (VehicleType type : VehicleType.values()) {
availableSpots.put(type, new LinkedList<>());
}
}
public void addSpot(ParkingSpot spot) {
availableSpots
.get(spot.getSupportedType())
.offer(spot);
}
public Ticket park(Vehicle vehicle) {
Queue<ParkingSpot> queue =
availableSpots.get(vehicle.getType());
ParkingSpot spot = queue.poll();
if (spot == null) {
return null;
}
spot.park(vehicle);
return new Ticket(vehicle, spot);
}
public double exit(Ticket ticket) {
ParkingSpot spot = ticket.getSpot();
spot.removeVehicle();
availableSpots
.get(spot.getSupportedType())
.offer(spot);
return ticket.calculateCharge();
}
}
This avoids scanning all parking spots and makes parking allocation nearly O(1). A production system should also support multiple entry and exit gates operating concurrently.
Since multiple threads may attempt to allocate or release parking spots simultaneously, access to the shared parking data must be synchronized.
import java.util.concurrent.locks.ReentrantLock;
class ParkingLot {
private final ReentrantLock lock = new ReentrantLock();
public Ticket park(Vehicle vehicle) {
lock.lock();
try {
// Allocate parking spot
} finally {
lock.unlock();
}
}
public double exit(Ticket ticket) {
lock.lock();
try {
// Release parking spot
} finally {
lock.unlock();
}
}
}
For larger systems, finer-grained locking can improve concurrency by maintaining a separate lock for each VehicleType, floor, or parking zone instead of locking the entire parking lot.
This allows multiple vehicles to park or exit simultaneously as long as they operate on different resources.