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() | +------------------+
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
UML Relationships Explained:
Inheritance (Generalization) - Solid Line with Empty Triangle:
Vehicle <|-- Motorcycle Vehicle <|-- Car Vehicle <|-- Truck Vehicle <|-- ElectricCar
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
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
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
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
UML Notation
PricingStrategy <|.. HourlyPricingStrategy PricingStrategy <|.. FlatRatePricingStrategy
- 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
- 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)