DEV Community

Cover image for Parking Lot System Design (LLD in Action)
AnkitDevCode
AnkitDevCode

Posted on • Edited on

Parking Lot System Design (LLD in Action)

What is Low-Level Design (LLD)?

Low-Level Design (LLD) is the process of translating High-Level Design (HLD) into concrete implementation (like class diagrams, interfaces, object relationships, and design patterns) that can be directly implemented in code.

Core Components of LLD

You can find a more in-depth information of this concept in my earlier post Object-oriented programming System(OOPs)

  • Classes and Objects
  • Interfaces and Abstractions
  • Relationships Between Classes

Key Considerations for LLD

  • Scope & Requirements
  • Clear Data Models
  • Core Design Principles (SOLID & Beyond)
  • System Robustness & Scalability
  • Design Patterns & Reusability

Common Pitfalls to Avoid

  • God classes that handle too many responsibilities.
  • Inconsistent Data Models
  • Overuse of inheritance, leading to fragile hierarchies.
  • Ignoring non-functional requirements until it’s too late.
  • Overengineering: Don’t use complex patterns unless justified—keep it lean.

Parking Lot System

Problem Statement

Design a parking lot management system for a multi-story parking garage that can handle different types of vehicles, dynamic pricing, real-time availability tracking.

Focus: Low-Level Design (LLD), Object-Oriented Programming, System Architecture

Essential Questions to Ask:

  • What types of vehicles should the system support?
  • How many floors will the parking lot have?
  • What are the different parking spot sizes?
  • How should pricing work - hourly, flat rate, or both?
  • Do we need real-time availability tracking?
  • Should the system handle reservations?
  • What payment methods are supported?
  • Do we need administrative features?

❌ Red Flags - Poor Requirements Gathering

  • Jumping straight to code without understanding requirements
  • Making assumptions without asking questions
  • Not clarifying scope - building too much or too little
  • Ignoring edge cases early in the discussion

How to approach the problem statement ?

When approaching system design, there are two common strategies:

Top-down approach → Start with the big picture. Break the larger problem into smaller, manageable parts, and then continue refining each part step by step.

  • Recommended during the analysis and design phase.
  • Starts with the big picture of the system.
  • Breaks the system into smaller, manageable sub-systems step by step.
  • Focus is on defining responsibilities and interactions at higher levels before going into details.
  • Useful for requirements gathering and system architecture design.

Bottom-up approach → Start small. Focus first on solving the smaller, individual problems, and then integrate them together to form the complete solution.

  • Recommended during the implementation phase.
  • Uses structure diagrams (e.g., class diagrams) to represent system components.
  • Focuses on breaking the problem statement into smaller components without deep details initially.
  • Behavior diagrams (e.g., sequence diagrams) are used alongside to show how the system functions overall.
  • Helps in understanding both the structure and the functionality of the system in its early stages.

Good Approach - Identify Core Components

+---------------------+ 1..* +---------------------+ 1..* +---------------------+ | ParkingLot |<>----------| ParkingFloor |<>----------| ParkingSpot | +---------------------+ +---------------------+ +---------------------+ | - id: String | | - floorNumber: int | | - spotId: String | | - levels: List | | - spots: Map | | - isOccupied: bool | | - gates: Map | | - freeSpots: Map | | - vehicle: Vehicle | | - tickets: Map | +---------------------+ | - type: SpotType | +---------------------+ | + findSpot(vType) | +---------------------+ | + parkVehicle() | | + parkVehicle(v) | | | + unparkVehicle() | | + unparkVehicle(t) | | 1 | + processPayment() | | + getFreeSpots() | | +---------------------+ | + notifyObserver() | | +---------------------+ | | 1..* +-----------------+ +-----------------+ +-----------------+ | | Vehicle | | ParkingTicket | | Payment | | +-----------------+ +-----------------+ +-----------------+ | | - license: String | | - ticketId: int | | - amount: double| | | - type: VehicleType | | - entryTime: Date | | - method: String| | +-----------------+ | - spot: ParkingSpot| | - status: String| | | + getType() | +-----------------+ +-----------------+ | +-----------------+ | + getDuration() | | +-----------------+ | | | | +------------------+ +-----------------------+ | | PaymentProcessor|<-----| DefaultPaymentProcessor| | +------------------+ +-----------------------+ | | + process() | | +------------------+ | | +------------------+ +-----------------------+ | | PricingStrategy|<-----| HourlyPricingStrategy| | +------------------+ +-----------------------+ | | + calculateFee() | | +------------------+ | | +------------------+ | | ParkingObserver |<------------------------------------------------------+ +------------------+ | + update() | +------------------+ 
Enter fullscreen mode Exit fullscreen mode

Parking Lot LLD – Component Summary

1. ParkingLot

The central manager of the entire parking system.

Responsibilities:

  • Keeps track of multiple floors.
  • Handles parking and unparking.
  • Interacts with Payment and PricingStrategy.

Key Methods:

parkVehicle(vehicle): Finds an available spot and issues a ParkingTicket
unparkVehicle(ticket): Frees the spot and processes payment
processPayment(ticket, paymentMethod): Calculates fee and completes payment

2. ParkingFloor

Manages spots on a single floor.

Responsibilities:

  • Keeps track of all parking spots on the floor.
  • Notifies observers when a spot becomes occupied or free.

Key Methods:

notifyObserver(): Updates DisplayBoard or other systems
getAvailableSpot(vehicleType): Returns a free spot suitable for the vehicle

3. ParkingSpot

Represents a single parking slot.

Attributes:

spotNumber, spotType (COMPACT, LARGE, ELECTRIC, HANDICAPPED), isOccupied, vehicle

Methods:

assignVehicle(vehicle)
removeVehicle()
isOccupied()

4. ParkingTicket

Tracks a vehicle’s parking session.

Attributes:

ticketId, vehicle, spot, entryTime, exitTime

Methods:

getDuration(): Time elapsed since entry

5. Payment

Handles payment processing.

Attributes:

amount, method (CASH, CARD), status (PENDING, COMPLETED)

Methods:

process(): Completes the payment

6. PricingStrategy

Defines how fees are calculated.

calculateFee(duration): returns fee

Example Implementation:

HourlyPricingStrategy, FlatRatePricingStrategy

7. ParkingObserver

Used to update displays or other systems when a spot changes.

update(): Called when spot becomes free/occupied

Example: DisplayBoard implements ParkingObserver to show available spots.


UML Diagram Analysis - Parking Lot System

The UML class diagram illustrates a comprehensive parking lot management system that demonstrates multiple design patterns and object-oriented principles. Let me break down each section:

Please find full diagram on kroki

Class Structure:

Vehicle (Abstract) ├── Motorcycle ├── Car ├── Truck └── ElectricCar 
Enter fullscreen mode Exit fullscreen mode

UML Relationships Explained:

Inheritance (Generalization) - Solid Line with Empty Triangle:

Vehicle <|-- Motorcycle Vehicle <|-- Car Vehicle <|-- Truck Vehicle <|-- ElectricCar 
Enter fullscreen mode Exit fullscreen mode

Composition and Aggregation Relationships

Strong Composition (Filled Diamond):

  • ParkingLot "has many" ParkingFloor
  • ParkingFloor "has many" ParkingSpot
  • ParkingSpot "has one" Vehicle
ParkingLot "1" *-- "many" ParkingFloor ParkingFloor "1" *-- "many" ParkingSpot 
Enter fullscreen mode Exit fullscreen mode

Meaning:

  • Parking floors cannot exist without the parking lot
  • Parking spots cannot exist without their floor
  • Lifecycle dependency: Child objects destroyed when parent is destroyed

Weak Aggregation (Empty Diamond):

ParkingLot "1" o-- "many" ParkingObserver 
Enter fullscreen mode Exit fullscreen mode

Meaning:

  • Observers can exist independently of the parking lot
  • Loose coupling: Observers can be shared across multiple parking lots

Simple Association (Solid Line):

ParkingSpot --> Vehicle : parkedVehicle ParkingTicket --> ParkingSpot ParkingTicket --> Payment 
Enter fullscreen mode Exit fullscreen mode

Meaning:

  • Objects reference each other but are independent
  • Temporary relationships: Vehicle can move to different spots

Design Pattern Implementations

Strategy Pattern:

PricingStrategy (Interface) ├── HourlyPricingStrategy └── FlatRatePricingStrategy 
Enter fullscreen mode Exit fullscreen mode

UML Notation

PricingStrategy <|.. HourlyPricingStrategy PricingStrategy <|.. FlatRatePricingStrategy 
Enter fullscreen mode Exit fullscreen mode

  • Interface implementation relationship
  • Enables runtime strategy switching
  • Open/Closed Principle: Add new strategies without modifying existing code

Observer Pattern:

Relationship: ParkingLot "1" o-- "many" ParkingObserver 
Enter fullscreen mode Exit fullscreen mode
  • Loose coupling: ParkingLot doesn't know concrete observer types
  • Event-driven: Automatic notifications on state changes

Factory Pattern:

  • Factory creates Vehicle instances but doesn't store references
  • Encapsulates creation logic: Hides vehicle instantiation complexity

Singleton Pattern:

  • Single instance guarantee: Only one parking lot can exist
  • Global access point: Available throughout the application

Builder Pattern:

Purpose: Constructs complex ParkingLot objects step by step

  • Fluent interface: Method chaining for easy configuration
  • Complex construction: Handles multi-floor setup with different spot types

Key Relationships Analysis

1. Vehicle ↔ ParkingSpot Interaction

  • Bidirectional relationship: Each knows about the other when parked
  • Validation logic: Vehicle determines if it can fit in spot

2. ParkingLot as Central Coordinator

  • Hub pattern: Central point for all parking operations
  • Dependency injection: Strategy and observers injected at runtime

Interface vs Abstract Class Decisions

Interfaces Used:

  • PricingStrategy: Behavior-only contract (no shared data)
  • ParkingObserver: Event handling contract (no shared implementation)

Abstract Classes Used:

  • Vehicle: Shared data (licensePlate, color) + abstract behavior

Decision Criteria:

Interface when: Pure behavior contract, no shared data
Abstract class when: Shared data + some abstract behaviors


Encapsulation and Access Modifiers

Field Visibility Patterns:

Private Fields (-):

  • All internal state in ParkingSpot, Payment, ParkingTicket
  • Information hiding: Implementation details not exposed

Protected Fields (#):

  • Vehicle class fields accessible to subclasses
  • Inheritance support: Subclasses can access parent data

Public Methods (+):

  • All service methods and getters
  • Clear API: Well-defined public interface

Thread Safety Considerations

ReentrantLock Usage:

  • ParkingSpot has individual locks for fine-grained control
  • ParkingFloor has floor-level locks for coordination
  • ParkingLot has system-wide locks for complex operations

Thread-Safe Collections:

  • Maps shown as ConcurrentHashMap implementations
  • Concurrent access: Multiple threads can safely access shared data

🧩 LLD Implementation

What You’ll Find in the Repository - ParkingLot implementation

  • All core components, including:
  • Vehicle and its subclasses (e.g., Car, Bike, Truck)
  • ParkingSpot, ParkingFloor
  • ParkingTicket
  • PricingStrategy with a sample HourlyPricingStrategy
  • Payment
  • PaymentProcessor
  • ParkingLot as the central controller
  • Basic Observer pattern implementation with ParkingObserver and DisplayBoard
  • Simple and clean code, designed to be easy to understand and extend for beginners.
  • README documentation that explains how to set up, run the project, and test the functionality.

📌 Note
This repository is a work in progress. The current design and implementation may have some limitations or areas that can be improved. I will continue to update and optimize the design over time to make it more robust and extensible.

For now, consider this as a good starting point to understand the Parking Lot LLD in Java. It demonstrates the core concepts and relationships clearly, while leaving room for enhancements and refinements.


Summary: Good vs Bad Approaches

✅ What Makes This Solution Strong:

  • Clear Separation of Concerns: Each class has a single responsibility
  • Proper Abstraction: Abstract classes and interfaces used appropriately
  • Thread Safety: Comprehensive concurrency handling
  • Design Patterns: Multiple patterns used correctly (Strategy, Factory, Singleton, Observer)
  • Extensibility: Easy to add new features without breaking existing code
  • Error Handling: Graceful handling of edge cases
  • Type Safety: Enums prevent invalid states and types

🚩 Common Red Flags to Avoid:

  • God Class Anti-pattern: Single class doing everything
  • Primitive Obsession: Using strings/ints instead of proper types
  • No Thread Safety: Ignoring concurrent access issues
  • Tight Coupling: Classes knowing too much about each other
  • No Error Handling: Not handling edge cases
  • Hard-coded Values: No flexibility for configuration
  • Missing Abstractions: Not using inheritance where appropriate
  • Poor Naming: Unclear or misleading class/method names

References & Credits

AI tools were used to assist in research and writing but final content was reviewed and verified by the author.

Top comments (0)