Parking Lot Project Low Level Design

 

🚗 Parking Lot Project — Interview Summary

🎯 Purpose

This project simulates a Parking Lot Management System using Spring Boot + Core Java OOP Design Patterns.
It allows:

  • Adding parking floors and parking spots

  • Parking and unparking vehicles

  • Generating parking tickets

  • Calculating parking fees using pricing strategies

  • Making payments using different payment strategies


⚙️ High-Level Architecture

📦 1. Entity Layer (entity package)

Represents the core data objects:

  • Vehicle

    • Contains vehicle number and type (CAR, BIKE, TRUCK)

  • ParkingSpot

    • Has id, allowedType, and occupied status (AtomicBoolean for thread-safety)

  • ParkingFloor

    • Has id and multiple ParkingSpot objects in a ConcurrentHashMap

  • Ticket

    • Contains ticketId, entryTime, exitTime, vehicle, floorId, spotId, and paymentStatus


⚙️ 2. Enum Layer (enumpack package)

Defines system constants:

  • VehicleTypeCAR, BIKE, TRUCK

  • PaymentModeCASH, UPI, CARD

  • PricingStrategyTypeTIME_BASED, FLAT_RATE


⚙️ 3. Strategy Layer (strategy package)

Implements the Strategy Design Pattern.

  • Pricing Strategy (strategy.pricing)

    • Interface: PricingStrategy

    • Implementations: TimeBasedPricing, FlatRatePricing

    • Chosen via PricingStrategyFactory

  • Payment Strategy (strategy.payment)

    • Interface: PaymentStrategy

    • Implementations: CashPayment, UpiPayment, CardPayment

    • Chosen via PaymentStrategyFactory

  • PaymentProcessor

    • A utility class that executes the payment using a selected PaymentStrategy.

✅ This pattern lets you switch or extend new pricing or payment logic without changing main code.


⚙️ 4. Factory Layer (factory package)

  • VehicleFactory → creates Vehicle objects based on type

  • PricingStrategyFactory → returns correct PricingStrategy instance

  • PaymentStrategyFactory → returns correct PaymentStrategy instance

✅ This uses Factory Design Pattern to centralize object creation.


⚙️ 5. Service Layer (services package)

  • ParkingLot (Singleton)

    • Central brain of the system

    • Maintains:

      • All ParkingFloor objects

      • All active Ticket objects

    • Responsibilities:

      • addFloor()

      • parkVehicle() → finds available spot and generates ticket

      • unparkVehicle() → calculates fee, takes payment, and vacates spot

      • printStatus() → shows all floors and their spot statuses

✅ This uses the Singleton Design Pattern so only one ParkingLot object exists globally.


⚙️ 6. Main Runner (Spring Boot)

  • ParkingALotApplication

    • Implements CommandLineRunner

    • On application startup, it:

      • Creates a floor with multiple spots

      • Creates vehicles and parks them

      • Prints status

      • Unparks vehicles and does payment

      • Prints final status



🔁 Logical Flow (Runtime Flow)

Here’s what happens when the app runs:


Application Start

      |

      v

 Create ParkingLot (Singleton)

      |

      v

 Add ParkingFloor -> Add ParkingSpots

      |

      v

 parkVehicle(vehicle)

     - findAvailableSpot() on each floor

     - if found: tryOccupy() spot atomically

     - create Ticket (ticketId, floorId, spotId, entryTime)

     - store ticket in activeTickets

      |

      v

 unparkVehicle(ticketId)

     - get ticket from activeTickets

     - calculate fee using pricingStrategy

     - get paymentStrategy using PaymentStrategyFactory

     - process payment

     - if payment success: vacate spot, remove ticket



📐 Design Patterns Used

PatternUsed InPurpose
SingletonParkingLotOnly one global ParkingLot manager
FactoryVehicleFactory, PaymentStrategyFactory, PricingStrategyFactoryCentralized object creation
StrategyPricing & Payment modulesEasily switch algorithms at runtime
Builder (optional)Ticket (if Lombok used)Object creation (replaced with setters now)
CommandLineRunnerMain classBootstrapping logic on app start




✅ Interview Selling Points

  • Thread-safe parking spots using AtomicBoolean

  • Scalable architecture — easy to add new vehicle types, pricing models, payment types

  • Clean separation of responsibilities (SRP - Single Responsibility Principle)

  • Extensible design using Strategy + Factory patterns

  • Demonstrates real-world system design principles



  🚗 Parking Lot System – Start to End Flow


1. Entry Point – Vehicle Arrives

  • A vehicle comes at the entry gate.

  • System needs to:

    1. Identify vehicle type (Car, Bike, Truck).

    2. Find a free spot suitable for that type.

👉 Handled by: ParkingLot.parkVehicle(vehicle, entryTime)



2. Finding a Spot

  • System iterates over all floors (ParkingFloor).

  • Each floor checks its ParkingSpots.

  • Conditions to allocate:

    • Spot supports same vehicle type.

    • Spot is not occupied.

  • If free → tryOccupy() marks it atomically occupied (thread-safe).

👉 Outcome: A unique spot is reserved.


3. Issuing a Ticket

  • Once spot is found → create a Ticket.

  • Ticket contains:

    • Ticket ID (UUID).

    • Vehicle details.

    • Floor ID + Spot ID.

    • Entry time.

  • Ticket stored in activeTickets map.

👉 Outcome: Driver gets a proof of parking.


4. Parking Status During Stay

  • Admin/system can check printStatus().

  • Shows:

    • Each floor.

    • Each spot → Occupied or Free.

  • Helps in monitoring.

5. Exit – Vehicle Leaves

  • Driver provides Ticket ID at exit.

  • System fetches ticket from activeTickets.

  • If ticket invalid → reject.

👉 Handled by: ParkingLot.unparkVehicle(ticketId, exitTime, paymentMode)


6. Fee Calculation

  • System calculates charges using PricingStrategy.

  • Example strategies:

    • Time-based → Normal vs Peak hours.

    • Event-based → Fixed per hour.

👉 Flexible, because it uses Strategy Pattern.


7. Payment Processing

  • Based on driver’s choice → UPI, Cash, Card.

  • System picks correct PaymentStrategy via factory.

  • Processes payment using PaymentProcessor.

  • If payment fails → don’t allow exit.

8. Freeing the Spot

  • If payment success:

    • Locate the spot (from ticket).

    • Call vacate() → mark it free.

    • Remove ticket from activeTickets.

👉 Outcome: Vehicle exits, spot becomes available.

9. End State

  • Vehicle is out.

  • Ticket no longer active.

  • Parking space ready for next vehicle.

🔄 Complete Flow Recap

  1. Vehicle enters → parkVehicle.

  2. Find free spot → allocate & mark occupied.

  3. Issue ticket → store in activeTickets.

  4. Vehicle stays → status can be printed.

  5. Vehicle exits → unparkVehicle.

  6. Fetch ticket → calculate fee.

  7. Process payment (UPI/Cash/Card).

  8. Free the spot → remove ticket.

  9. System updated, ready for next cycle.


📐 Parking Lot – UML / Architecture Overview


1. Core Entities (Domain Layer)

Vehicle

  • Variables: id, type (VehicleType)

  • Methods: getters

ParkingSpot

  • Variables: id, allowedType, occupied (AtomicBoolean)

  • Methods:

    • tryOccupy() – atomically mark spot as taken

    • vacate() – free spot

    • isOccupied() – check status

ParkingFloor

  • Variables: id, spots (Map<spotId, ParkingSpot>)

  • Methods:

    • addSpot(ParkingSpot)

    • findAvailableSpot(VehicleType)Optional<ParkingSpot>

    • vacateSpot(String spotId)

Ticket

  • Variables: ticketId, vehicle, entryTime, floorId, spotId

  • Methods: getters/setters


2. Services (Business Logic Layer)

ParkingLot (Singleton)

  • Variables:

    • floors (Map<floorId, ParkingFloor>)

    • activeTickets (Map<ticketId, Ticket>)

    • pricingStrategy (PricingStrategy)

  • Methods:

    • addFloor(ParkingFloor)

    • parkVehicle(Vehicle, entryTime)Ticket

    • unparkVehicle(ticketId, exitTime, PaymentMode)

    • printStatus()


3. Strategies (Behavioral Layer – Strategy Pattern)

PricingStrategy (interface)

  • calculateFee(VehicleType, entryTime, exitTime)

Implementations:

  • TimeBasedPricing – fee = hours * rate

  • FlatPricing – fixed fee

PaymentStrategy (interface)

  • pay(Ticket, fee)

Implementations:

  • CashPayment

  • CardPayment

  • UPIPayment


4. Factories (Creational Layer – Factory Pattern)

PricingStrategyFactory

  • get(PricingStrategyType) → returns correct strategy

PaymentStrategyFactory

  • get(PaymentMode) → returns correct payment strategy


5. Supporting Enums

  • VehicleTypeCAR, BIKE, TRUCK

  • GateTypeENTRY, EXIT

  • PricingStrategyTypeTIME_BASED, FLAT

  • PaymentModeCASH, CARD, UPI


🚦 High-Level Flow (Revision)

  1. Vehicle arrives at EntryGate
    ParkingLot.parkVehicle()
    ParkingFloor.findAvailableSpot()
    ParkingSpot.tryOccupy()
    Ticket generated

  2. Vehicle exits via ExitGate
    ParkingLot.unparkVehicle()
    PricingStrategy.calculateFee()
    PaymentStrategy.pay()
    Spot.vacate()
    Ticket removed from active list



 step-by-step flow

Entry flow (park)

  1. Vehicle arrives at EntryGate → Gate collects vehicle info (license, type).

  2. EntryGate calls ParkingLot.parkVehicle(vehicle, entryTime) (delegation).

  3. ParkingLot:

    • Iterates floors: floor.findAvailableSpot(vehicle.getType())

      • ParkingFloor.findAvailableSpot(...) iterates spots and calls spot.tryOccupy() (atomic).

    • If a spot is found: ParkingLot creates Ticket (single responsibility):

      • ticketId, entryTime, vehicle, floorId, spotId, paymentStatus = PENDING

    • Store ticket in activeTickets map (ticketId → Ticket).

    • Return Ticket to EntryGate (so gate can open and hand ticket to driver).

  4. EntryGate shows ticket and opens gate.

Notes: tryOccupy() prevents race conditions. Only ParkingLot writes to activeTickets.


public Ticket parkVehicle(Vehicle vehicle, LocalDateTime entryTime) {
for (ParkingFloor floor : floors.values()) {
Optional<ParkingSpot> spotOpt = floor.findAvailableSpot(vehicle.getType());
if (spotOpt.isPresent()) {
ParkingSpot spot = spotOpt.get();

String ticketId = UUID.randomUUID().toString();

// Use setters instead of builder
Ticket ticket = new Ticket();
ticket.setTicketId(ticketId);
ticket.setVehicle(vehicle);
ticket.setEntryTime(entryTime);
ticket.setFloorId(floor.getId());
ticket.setSpotId(spot.getId());

activeTickets.put(ticketId, ticket);

System.out.println("Vehicle parked. Ticket: " + ticketId);
return ticket;
}
}

System.out.println("No spot available for vehicle type: " + vehicle.getType());
return null;
}


public Optional<ParkingSpot> findAvailableSpot(VehicleType vehicleType) {
for (ParkingSpot spot : spots.values()) {
if (spot.getAllowedType() == vehicleType && spot.tryOccupy()) {
return Optional.of(spot);
}
}
return Optional.empty();
}
public VehicleType getAllowedType() {
return allowedType;
}
public boolean tryOccupy() {
return occupied.compareAndSet(false, true);
}


package com.parking.demo;

import enumpack.VehicleType;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import services.ParkingLot;
import entity.ParkingFloor;
import entity.ParkingSpot;
import entity.Vehicle;
import factory.VehicleFactory;
import enumpack.PaymentMode;

import java.time.LocalDateTime;

@SpringBootApplication
public class ParkingALotApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(ParkingALotApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
// Get singleton ParkingLot instance
ParkingLot parkingLot = ParkingLot.getInstance();

// Create and add a floor with spots
ParkingFloor floor1 = new ParkingFloor("F1");
floor1.addSpot(new ParkingSpot("S1", VehicleType.CAR));
floor1.addSpot(new ParkingSpot("S2", VehicleType.BIKE));
floor1.addSpot(new ParkingSpot("S3", VehicleType.TRUCK));
parkingLot.addFloor(floor1);

// Park a Car
Vehicle car = VehicleFactory.create("MH12AB1234", VehicleType.CAR);
LocalDateTime entryTime = LocalDateTime.now().minusHours(2); // parked 2 hours ago
var carTicket = parkingLot.parkVehicle(car, entryTime);

// Park a Bike
Vehicle bike = VehicleFactory.create("DL10XY9999", VehicleType.BIKE);
LocalDateTime bikeEntry = LocalDateTime.now().minusHours(1); // parked 1 hour ago
var bikeTicket = parkingLot.parkVehicle(bike, bikeEntry);

// Print parking lot status
System.out.println("\n--- Parking Lot Status ---");
parkingLot.printStatus();

// Unpark vehicles and pay
System.out.println("\n--- Unparking Vehicles ---");
if (carTicket != null) {
parkingLot.unparkVehicle(carTicket.getTicketId(), LocalDateTime.now(), PaymentMode.UPI);
}
if (bikeTicket != null) {
parkingLot.unparkVehicle(bikeTicket.getTicketId(), LocalDateTime.now(), PaymentMode.CASH);
}

// Print status after unparking
System.out.println("\n--- Parking Lot Status After Exit ---");
parkingLot.printStatus();
}
}


private final Map<String, ParkingSpot> spots = new ConcurrentHashMap<>();


Key → Value

  • Key: spotId (String) → unique identifier of the parking spot, e.g., "S1", "S2", "B1".

  • Value: ParkingSpot → the actual spot object which holds:

    • id

    • allowedType (VehicleType: CAR, BIKE, TRUCK)

    • occupied (AtomicBoolean)



spots = {
    "S1" → ParkingSpot(id="S1", allowedType=CAR, occupied=false),
    "S2" → ParkingSpot(id="S2", allowedType=BIKE, occupied=true),
    "S3" → ParkingSpot(id="S3", allowedType=TRUCK, occupied=false)
}

Why Map?

  • Fast lookup by spot ID (spots.get(spotId) when vacating).

  • Thread-safe iteration + atomic operations (ConcurrentHashMap + tryOccupy()).


private final Map<String, ParkingFloor> floors = new HashMap<>();
private final Map<String, Ticket> activeTickets = new HashMap<>();

floors Map

  • Key: floorId (String) → e.g., "F1", "F2"

  • Value: ParkingFloor object

Example

floors = {
    "F1" → ParkingFloor(id="F1", spots={...}),
    "F2" → ParkingFloor(id="F2", spots={...})
}


ParkingFloor F1
id = "F1"
spots = {
    "S1" → ParkingSpot(id="S1", allowedType=CAR, occupied=false),
    "S2" → ParkingSpot(id="S2", allowedType=BIKE, occupied=true),
    "S3" → ParkingSpot(id="S3", allowedType=TRUCK, occupied=false)
}


activeTickets = {
    "a1b2c3" → Ticket(ticketId="a1b2c3", vehicle=Car(...), floorId="F1", spotId="S1", entryTime=..., paymentStatus=PENDING),
    "d4e5f6" → Ticket(ticketId="d4e5f6", vehicle=Bike(...), floorId="F1", spotId="S2", entryTime=..., paymentStatus=PENDING)
}




Complete Code Link --shivamofficial/Parkinglot-LLD


...

Comments

Popular posts from this blog

Two Sum II - Input Array Is Sorted

Comparable Vs. Comparator in Java

Increasing Triplet Subsequence