📐 Complete Learning Guide

Master Low Level
Design (LLD)

From absolute zero to interview-ready. Learn OOP, SOLID principles, Design Patterns, UML diagrams, and real-world system design — all in one place.

8+
Topics
23+
Patterns
40+
Code Examples
5
Case Studies
Scroll to begin

What is LLD?

Low Level Design (LLD) is the art of translating requirements into working code architecture — thinking deeply about how classes, objects, and components interact with each other.

Definition
Low Level Design (LLD) is the process of designing the internal structure of individual components or modules of a software system, focusing on class design, relationships, algorithms, and data structures — bridging the gap between High Level Design and actual code.
💡
LLD vs HLD
HLD (High Level Design) answers "What are the major components?" — e.g., microservices, databases, APIs. LLD (Low Level Design) answers "How does each component work internally?" — classes, methods, design patterns.
🏗️
Class Design

Defining classes, their attributes, methods, and access modifiers with clear responsibility.

🔗
Relationships

How classes relate — inheritance, composition, aggregation, and association.

🧩
Design Patterns

Reusable solutions to common design problems — Singleton, Factory, Observer, etc.

🏦
Real World Analogy
Think of it like building a bank
HLD: "The bank has ATMs, branches, online portal, and a central database."

LLD: "The ATM has a CardReader class, a CashDispenser class, a Transaction class. The Transaction class has methods withdraw(), deposit(), getBalance(). The CardReader validates via AuthenticationService..."

Why LLD Matters

1

Maintainability

Well-designed code is easy to change without breaking other parts. Good LLD means you can add features without rewriting everything.

2

Scalability

Proper design patterns allow the system to grow. Adding a new payment method shouldn't require touching existing payment code.

3

Reusability

Good LLD creates components that can be used across different parts of the system without copy-pasting code.

4

Testability

Well-designed classes are easy to unit test. Tight coupling makes testing nearly impossible.

Chapter 02

OOP Foundations

Object-Oriented Programming is the bedrock of LLD. Master these 4 pillars and everything else becomes intuitive.

Encapsulation
Bundling data (attributes) and the methods that operate on that data into a single unit (class), and restricting direct access to some of the object's components.
🏧
Real World
ATM Machine
You interact with an ATM through buttons and a screen — you never touch the internal cash mechanism. The internal state (cash count, algorithms) is hidden. You only see the public interface (withdraw, deposit, check balance).
Java
public class BankAccount {
    // Private — hidden from outside world
    private String accountNumber;
    private double balance;
    private String pin;

    public BankAccount(String accNum, String pin, double initialBalance) {
        this.accountNumber = accNum;
        this.pin = pin;
        this.balance = initialBalance;
    }

    // Public methods — the controlled interface
    public boolean withdraw(double amount, String pin) {
        if (!validatePin(pin)) return false;
        if (amount > balance) return false;
        balance -= amount;
        return true;
    }

    public double getBalance(String pin) {
        if (!validatePin(pin)) throw new SecurityException("Invalid PIN");
        return balance;
    }

    // Private — internal logic hidden
    private boolean validatePin(String inputPin) {
        return this.pin.equals(inputPin);
    }
}
Inheritance
A mechanism where a new class (child/subclass) derives properties and behaviors from an existing class (parent/superclass), promoting code reuse and establishing an "is-a" relationship.
🚗
Real World
Vehicle Types
A Car IS-A Vehicle. A Truck IS-A Vehicle. Both share common properties (speed, fuel, engine) from Vehicle but each has unique behaviors (Car: sunroof, Truck: payload).
Inheritance Hierarchy
«abstract»Vehicle
brand: String
speed: int
fuel: String
start(): void
stop(): void
fuelUp(): void
Car
doors: int
sunroof: boolean
honk(): void
park(): void
Truck
payload: int
axles: int
loadCargo(): void
getPayload(): int
Java
abstract class Vehicle {
    protected String brand;
    protected int speed;

    public Vehicle(String brand) { this.brand = brand; }

    public void start() { System.out.println(brand + " engine starting..."); }
    public abstract void fuelUp();  // must be implemented by subclasses
}

class Car extends Vehicle {
    private boolean hasSunroof;

    public Car(String brand, boolean sunroof) {
        super(brand);  // calls parent constructor
        this.hasSunroof = sunroof;
    }

    @Override
    public void fuelUp() { System.out.println("Filling petrol at gas station"); }

    public void park() { System.out.println(brand + " is parking"); }
}

class ElectricCar extends Car {
    private int batteryCapacity;

    @Override
    public void fuelUp() { System.out.println("Charging battery..."); }
}
Polymorphism
The ability of an object to take many forms. The same interface (method name) behaves differently depending on the actual object type. "One interface, many implementations."
🎵
Real World
Musical Instruments
Every instrument can play() — but how? A Piano plays keys, a Guitar strums strings, a Drum gets hit. Same action name, completely different behavior. You can say "play all instruments" without knowing the specific type!
Java — Runtime Polymorphism
abstract class Shape {
    public abstract double area();   // each shape implements differently
    public abstract double perimeter();
}

class Circle extends Shape {
    private double radius;
    public Circle(double r) { this.radius = r; }

    @Override public double area() { return Math.PI * radius * radius; }
    @Override public double perimeter() { return 2 * Math.PI * radius; }
}

class Rectangle extends Shape {
    private double width, height;
    public Rectangle(double w, double h) { this.width=w; this.height=h; }

    @Override public double area() { return width * height; }
    @Override public double perimeter() { return 2 * (width + height); }
}

// Polymorphism in action!
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(5));
shapes.add(new Rectangle(4, 6));

for (Shape s : shapes) {
    // JVM decides at runtime which area() to call
    System.out.println("Area: " + s.area());
}
Abstraction
Hiding complex implementation details and showing only the essential features of an object. It defines WHAT an object does, not HOW it does it.
📱
Real World
Smartphone
You tap "Call" without knowing how radio frequencies are modulated, how your voice is encoded, or how packets travel across networks. The interface is simple even though the implementation is enormously complex.
Java — Interface vs Abstract Class
// Interface: Pure abstraction — defines WHAT, not HOW
interface PaymentGateway {
    boolean processPayment(double amount, String currency);
    boolean refund(String transactionId);
    String getTransactionStatus(String id);
}

// Concrete implementations — the HOW
class StripeGateway implements PaymentGateway {
    @Override
    public boolean processPayment(double amount, String currency) {
        // Stripe-specific API calls...
        return true;
    }
    // ... other implementations
}

class PayPalGateway implements PaymentGateway {
    @Override
    public boolean processPayment(double amount, String currency) {
        // PayPal-specific API calls...
        return true;
    }
}

// Client code doesn't care about implementation!
class CheckoutService {
    private PaymentGateway gateway;  // uses interface type

    public CheckoutService(PaymentGateway gateway) {
        this.gateway = gateway;
    }

    public void checkout(double amount) {
        gateway.processPayment(amount, "USD");  // works with any gateway!
    }
}
Key Insight
By depending on the PaymentGateway interface, you can swap Stripe for PayPal without touching CheckoutService. This is the power of abstraction combined with dependency injection.
Chapter 03

SOLID Principles

Five design principles that make software more understandable, flexible, and maintainable. Every senior engineer swears by these.

🧠
SOLID Acronym
Single Responsibility  ·  Open/Closed  ·  Liskov Substitution  ·  Interface Segregation  ·  Dependency Inversion
S
S — Single Responsibility
SRP · Single Responsibility Principle
A class should have only one reason to change. Each class does exactly one thing and does it well.
O
O — Open/Closed
OCP · Open-Closed Principle
Classes should be open for extension but closed for modification. Add new features without changing existing code.
L
L — Liskov Substitution
LSP · Liskov Substitution Principle
Objects of a subclass should be replaceable for objects of the superclass without breaking the program.
I
I — Interface Segregation
ISP · Interface Segregation Principle
Clients should not be forced to depend on interfaces they don't use. Prefer many small, focused interfaces.
D
D — Dependency Inversion
DIP · Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces). Don't depend on concrete classes — depend on interfaces.

S — Single Responsibility (Deep Dive)

❌ Violates SRP
  • UserService handles login logic
  • UserService also sends emails
  • UserService also formats user data
  • UserService also writes to database
✅ Follows SRP
  • AuthService → login/logout
  • EmailService → send emails
  • UserFormatter → format data
  • UserRepository → database ops
Java — SRP Example
// ❌ BAD: This class has TOO MANY responsibilities
class UserService_BAD {
    public void registerUser(User user) { /* ... */ }
    public void sendWelcomeEmail(User user) { /* ... */ }  // wrong place!
    public void saveToDatabase(User user) { /* ... */ }    // wrong place!
    public String generateReport() { /* ... */ }             // wrong place!
}

// ✅ GOOD: Each class has ONE job
class UserService {      // Only handles user business logic
    private UserRepository repo;
    private EmailService emailSvc;

    public void registerUser(User user) {
        repo.save(user);           // delegates DB work
        emailSvc.sendWelcome(user); // delegates email work
    }
}

class UserRepository {   // Only handles database operations
    public void save(User user) { /* DB logic */ }
    public User findById(long id) { /* DB logic */ return null; }
}

class EmailService {     // Only handles email sending
    public void sendWelcome(User user) { /* Email logic */ }
    public void sendReset(User user) { /* Email logic */ }
}

O — Open/Closed Principle (Deep Dive)

🏪
Real World: Discount System
Adding new discount types without modifying existing code
An e-commerce app needs discounts: seasonal, loyalty, coupon. Bad design: one big if-else block per discount type — every new discount requires modifying existing code. Good design: a DiscountStrategy interface — just add a new class for each new discount!
Java — OCP with Strategy Pattern
// Open for extension (add new classes) ✅
// Closed for modification (don't touch this interface) ✅
interface DiscountStrategy {
    double apply(double price);
}

class SeasonalDiscount implements DiscountStrategy {
    @Override
    public double apply(double price) { return price * 0.80; } // 20% off
}

class LoyaltyDiscount implements DiscountStrategy {
    @Override
    public double apply(double price) { return price * 0.90; } // 10% off
}

// Adding a new discount? Just add a new class — no existing code touched!
class CouponDiscount implements DiscountStrategy {
    private double couponAmount;
    public CouponDiscount(double amount) { this.couponAmount = amount; }

    @Override
    public double apply(double price) { return price - couponAmount; }
}

class PriceCalculator {
    public double calculate(double price, DiscountStrategy discount) {
        return discount.apply(price);  // never changes regardless of discount type
    }
}
Chapter 04

DRY · KISS · YAGNI

Three golden rules that every professional developer follows to keep code clean and sane.

🔁
DRY
Don't Repeat Yourself

Every piece of knowledge must have a single, unambiguous, authoritative representation in the system. If you copy-paste code, you're violating DRY.

😌
KISS
Keep It Simple, Stupid

Most systems work best if they are kept simple rather than made complicated. Prefer simple solutions over clever ones.

🚫
YAGNI
You Aren't Gonna Need It

Don't add functionality until it's actually needed. Premature optimization and over-engineering are the enemy.

Java — DRY Example
// ❌ WET (Write Everything Twice) — DON'T do this
public String getFullNameForEmail(User user) {
    return user.getFirstName() + " " + user.getLastName();
}

public String getFullNameForReport(User user) {
    return user.getFirstName() + " " + user.getLastName(); // duplicated!
}

public String getFullNameForInvoice(User user) {
    return user.getFirstName() + " " + user.getLastName(); // duplicated again!
}

// ✅ DRY — Single source of truth
public String getFullName(User user) {
    return user.getFirstName() + " " + user.getLastName();
}

// Now every place calls getFullName() — change once, fixed everywhere!
Chapter 05

Creational Patterns

Patterns that deal with object creation mechanisms. They provide ways to create objects while hiding the creation logic.

1️⃣
Creational
Singleton Pattern
Very Common
Definition
Ensures a class has only ONE instance and provides a global point of access to it.
🖨️
Real World: Printer Spooler
A company has ONE printer. Multiple applications send print jobs to it, but there's only one print queue. A Singleton ensures all apps share the same printer instance — no duplicates, no conflicts.
Java — Thread-Safe Singleton
public class DatabaseConnection {
    // volatile ensures visibility across threads
    private static volatile DatabaseConnection instance = null;

    // Private constructor — no one can create it from outside
    private DatabaseConnection() {
        // Expensive initialization: connect to DB, setup pools...
        System.out.println("DB Connection established");
    }

    // Double-checked locking for thread safety
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnection.class) {
                if (instance == null) {
                    instance = new DatabaseConnection();
                }
            }
        }
        return instance;
    }

    public void query(String sql) { /* execute query */ }
}

// Usage
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
// db1 == db2 → ALWAYS TRUE
⚠️
When to Use
Configuration managers, Logger, Cache, Thread Pool, Registry settings — anything that should have exactly one instance in the application.
🏭
Creational
Factory Method Pattern
Essential
Definition
Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
🚚
Real World: Logistics Company
A logistics company delivers by Truck (road) or Ship (sea). The business logic (plan route, calculate cost) is the same — only the transport type changes. The factory decides which transport object to create based on the order.
Java — Factory Pattern
interface Notification {
    void send(String message);
}

class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("📧 Email: " + message);
    }
}

class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("📱 SMS: " + message);
    }
}

class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("🔔 Push: " + message);
    }
}

// The Factory — knows which object to create
class NotificationFactory {
    public static Notification create(String type) {
        switch (type.toLowerCase()) {
            case "email": return new EmailNotification();
            case "sms":   return new SMSNotification();
            case "push":  return new PushNotification();
            default: throw new IllegalArgumentException("Unknown type: " + type);
        }
    }
}

// Client code
Notification n = NotificationFactory.create("email");
n.send("Your order has shipped!");
🔨
Creational
Builder Pattern
Very Common
Definition
Constructs complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction process.
🏠
Real World: House Builder
Building a house involves many steps: foundation, walls, roof, windows, garage, pool (optional). A director (builder) coordinates the construction steps. You can build a basic house or a luxury mansion using the same building process — just different configurations.
Java — Builder Pattern
public class Pizza {
    private String size;
    private String crust;
    private String sauce;
    private List<String> toppings;
    private boolean extraCheese;

    // Private — only Builder can create this
    private Pizza(Builder builder) {
        this.size = builder.size;
        this.crust = builder.crust;
        this.sauce = builder.sauce;
        this.toppings = builder.toppings;
        this.extraCheese = builder.extraCheese;
    }

    public static class Builder {
        private String size;
        private String crust = "thin";  // defaults
        private String sauce = "tomato";
        private List<String> toppings = new ArrayList<>();
        private boolean extraCheese = false;

        public Builder(String size) { this.size = size; }

        public Builder crust(String crust) { this.crust = crust; return this; }
        public Builder sauce(String sauce) { this.sauce = sauce; return this; }
        public Builder topping(String t) { toppings.add(t); return this; }
        public Builder extraCheese() { this.extraCheese = true; return this; }
        public Pizza build() { return new Pizza(this); }
    }
}

// Clean, readable object creation!
Pizza myPizza = new Pizza.Builder("large")
    .crust("thick")
    .sauce("bbq")
    .topping("pepperoni")
    .topping("mushrooms")
    .extraCheese()
    .build();
Chapter 06

Structural Patterns

Patterns that deal with object composition — how classes and objects are composed to form larger structures.

🔌
Structural
Adapter Pattern
Important
Definition
Allows objects with incompatible interfaces to work together. Acts as a bridge between two incompatible interfaces — like a power adapter that converts plug types.
🔋
Real World: Power Adapter
Your laptop charger has a US plug but the European socket has a different shape. A travel adapter wraps the US plug and makes it fit the European socket. Similarly, an Adapter class wraps an incompatible class to fit the expected interface.
Java — Adapter Pattern
// Your code expects this interface
interface MediaPlayer {
    void play(String filename);
}

// But you have this third-party library with different interface
class VlcPlayer {
    public void playVlc(String filename) {
        System.out.println("VLC playing: " + filename);
    }
}

// Adapter bridges the gap
class VlcAdapter implements MediaPlayer {
    private VlcPlayer vlcPlayer;

    public VlcAdapter(VlcPlayer player) { this.vlcPlayer = player; }

    @Override
    public void play(String filename) {
        vlcPlayer.playVlc(filename);  // translates the call
    }
}

// Client uses MediaPlayer interface — doesn't know about VLC!
MediaPlayer player = new VlcAdapter(new VlcPlayer());
player.play("movie.vlc");
🎨
Structural
Decorator Pattern
Widely Used
Definition
Attaches additional responsibilities to an object dynamically. Provides a flexible alternative to subclassing for extending functionality — wraps an object to add new behavior.
Real World: Coffee Shop
Start with a basic Espresso. Add Milk → it's a Latte. Add Caramel → Caramel Latte. Each topping "decorates" the base coffee, adding cost and description. You can stack decorators infinitely!
Java — Decorator Pattern (Coffee)
interface Coffee {
    String getDescription();
    double getCost();
}

class Espresso implements Coffee {
    @Override public String getDescription() { return "Espresso"; }
    @Override public double getCost() { return 1.99; }
}

// Abstract decorator — wraps a Coffee
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    public CoffeeDecorator(Coffee c) { this.coffee = c; }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee c) { super(c); }

    @Override
    public String getDescription() { return coffee.getDescription() + ", Milk"; }
    @Override
    public double getCost() { return coffee.getCost() + 0.50; }
}

class CaramelDecorator extends CoffeeDecorator {
    public CaramelDecorator(Coffee c) { super(c); }

    @Override
    public String getDescription() { return coffee.getDescription() + ", Caramel"; }
    @Override
    public double getCost() { return coffee.getCost() + 0.75; }
}

// Stacking decorators!
Coffee myCoffee = new Espresso();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new CaramelDecorator(myCoffee);

System.out.println(myCoffee.getDescription()); // Espresso, Milk, Caramel
System.out.println("$" + myCoffee.getCost());   // $3.24
Chapter 07

Behavioral Patterns

Patterns that deal with object interaction and responsibilities — how objects communicate and collaborate.

👁️
Behavioral
Observer Pattern
Must Know
Definition
Defines a one-to-many dependency between objects. When one object (subject) changes state, all its dependents (observers) are automatically notified and updated. Also known as Publish-Subscribe.
📰
Real World: News Subscription
A newspaper publisher (Subject) publishes editions. Subscribers (Observers) get notified when a new edition is out. If you cancel subscription, you stop getting updates. The publisher doesn't know individual subscribers — it just notifies all of them.
Observer Pattern Flow
📦 Subject (EventSource)
state changes
notifyObservers()
iterates observers list
📧 EmailObserver.update()
📱 SMSObserver.update()
📊 LogObserver.update()
Java — Observer Pattern
interface Observer {
    void update(String event, Object data);
}

interface Subject {
    void subscribe(Observer observer);
    void unsubscribe(Observer observer);
    void notifyObservers(String event, Object data);
}

class OrderService implements Subject {
    private List<Observer> observers = new ArrayList<>();

    @Override public void subscribe(Observer o) { observers.add(o); }
    @Override public void unsubscribe(Observer o) { observers.remove(o); }

    @Override
    public void notifyObservers(String event, Object data) {
        for (Observer o : observers) o.update(event, data);
    }

    public void placeOrder(Order order) {
        // ... process order logic ...
        notifyObservers("ORDER_PLACED", order); // broadcast!
    }
}

// Multiple independent observers react to the same event
class EmailNotifier implements Observer {
    @Override
    public void update(String event, Object data) {
        if ("ORDER_PLACED".equals(event))
            System.out.println("Sending confirmation email...");
    }
}

class InventoryManager implements Observer {
    @Override
    public void update(String event, Object data) {
        if ("ORDER_PLACED".equals(event))
            System.out.println("Updating inventory...");
    }
}
♟️
Behavioral
Strategy Pattern
Essential
Definition
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Lets the algorithm vary independently from clients that use it.
🗺️
Real World: Google Maps Navigation
Google Maps lets you pick a strategy: Driving 🚗, Walking 🚶, Cycling 🚴, or Transit 🚌. The destination is the same, but the algorithm for calculating the route is completely different. Switching strategy doesn't break anything.
Java — Strategy Pattern (Sorting)
interface SortStrategy {
    void sort(int[] array);
}

class BubbleSort implements SortStrategy {
    @Override
    public void sort(int[] arr) {
        // O(n²) — good for small arrays
        System.out.println("Bubble sorting...");
    }
}

class QuickSort implements SortStrategy {
    @Override
    public void sort(int[] arr) {
        // O(n log n) average — good for large arrays
        System.out.println("Quick sorting...");
    }
}

// Context — uses a strategy
class DataSorter {
    private SortStrategy strategy;

    public DataSorter(SortStrategy strategy) { this.strategy = strategy; }

    // Can switch strategy at runtime!
    public void setStrategy(SortStrategy s) { this.strategy = s; }

    public void performSort(int[] data) {
        strategy.sort(data);
    }
}

// Usage
DataSorter sorter = new DataSorter(new BubbleSort());
sorter.performSort(smallArray);

sorter.setStrategy(new QuickSort());  // switch at runtime!
sorter.performSort(largeArray);
Chapter 08

UML & Class Diagrams

UML (Unified Modeling Language) is the standard visual language for designing and communicating software architecture. Class diagrams are the most important type for LLD.

Relationship Types

UML Relationship Reference
RelationshipSymbolMeaningExample
Inheritance──△ (solid + open arrow)IS-A relationshipDog IS-A Animal
Implementation- - △ (dashed + open arrow)Implements interfaceDog implements Runnable
Composition──◆ (solid diamond)HAS-A (strong ownership)Car HAS-A Engine (engine dies with car)
Aggregation──◇ (hollow diamond)HAS-A (weak ownership)Team HAS-A Player (player exists without team)
Association──→ (solid arrow)USES-A relationshipTeacher USES-A Student
Dependency- - → (dashed arrow)Depends on temporarilyMethod takes Object as param

Complete Class Diagram — Library System

Library Management System — UML Class Diagram
«interface»Searchable
search(query: String): List
filter(criteria): List
implements ↑
«abstract»LibraryItem
id: String
title: String
available: boolean
getId(): String
isAvailable(): bool
checkout(): void
Book
isbn: String
author: String
pages: int
Magazine
issue: int
month: String
Member
memberId: String
name: String
checkedOut: List
borrowItem(item): void
returnItem(item): void
getHistory(): List
manages ↕ 1..*
Library
name: String
items: List<LibraryItem>
members: List<Member>
addItem(item): void
registerMember(m): void
search(query): List
Chapter 09

Real-World Case Studies

Apply everything you've learned to real LLD interview problems. These are the most commonly asked questions in product companies.

Parking Lot System

🎯
Requirements
The parking lot has multiple floors, multiple spot types (Small, Medium, Large, Handicapped). Vehicles of different types park in appropriate spots. Track free/occupied spots. Support entry and exit with ticketing.

Key Classes Identified

Entities
ParkingLot
ParkingFloor
ParkingSpot
Vehicle
Ticket
Enums
SpotType
VehicleType
SpotStatus
TicketStatus
Patterns Used
Singleton (ParkingLot)
Strategy (Pricing)
Factory (Spot create)
Java — Parking Lot LLD
enum SpotType { SMALL, MEDIUM, LARGE, HANDICAPPED }
enum VehicleType { MOTORCYCLE, CAR, TRUCK }

abstract class Vehicle {
    protected String licensePlate;
    protected VehicleType type;

    public abstract boolean canFitInSpot(ParkingSpot spot);
}

class Car extends Vehicle {
    public Car(String plate) {
        this.licensePlate = plate;
        this.type = VehicleType.CAR;
    }

    @Override
    public boolean canFitInSpot(ParkingSpot spot) {
        return spot.getType() == SpotType.MEDIUM
            || spot.getType() == SpotType.LARGE;
    }
}

class ParkingSpot {
    private String spotId;
    private SpotType type;
    private boolean occupied = false;
    private Vehicle parkedVehicle;

    public ParkingSpot(String id, SpotType type) {
        this.spotId = id;
        this.type = type;
    }

    public boolean park(Vehicle vehicle) {
        if (occupied || !vehicle.canFitInSpot(this)) return false;
        parkedVehicle = vehicle;
        occupied = true;
        return true;
    }

    public void unpark() {
        parkedVehicle = null;
        occupied = false;
    }

    public SpotType getType() { return type; }
    public boolean isOccupied() { return occupied; }
}

class ParkingFloor {
    private List<ParkingSpot> spots;
    private int floorNumber;

    public ParkingSpot findAvailableSpot(Vehicle v) {
        return spots.stream()
            .filter(s -> !s.isOccupied() && v.canFitInSpot(s))
            .findFirst()
            .orElse(null);
    }
}

// Singleton Parking Lot
class ParkingLot {
    private static ParkingLot instance;
    private List<ParkingFloor> floors;
    private Map<String, Ticket> activeTickets = new HashMap<>();

    private ParkingLot() {}
    public static ParkingLot getInstance() {
        if (instance == null) instance = new ParkingLot();
        return instance;
    }

    public Ticket entry(Vehicle vehicle) {
        for (ParkingFloor floor : floors) {
            ParkingSpot spot = floor.findAvailableSpot(vehicle);
            if (spot != null) {
                spot.park(vehicle);
                Ticket ticket = new Ticket(vehicle, spot);
                activeTickets.put(ticket.getId(), ticket);
                return ticket;
            }
        }
        throw new RuntimeException("Parking lot is full!");
    }
}

Elevator System

🎯
Requirements
Multiple elevators in a building. Requests come from inside (floor button) and outside (hall button). An algorithm dispatches the optimal elevator. Each elevator has states: Moving Up, Moving Down, Idle. Handle concurrent requests efficiently.
Elevator State Machine
IDLE
request received
MOVING UP ↑
DOOR OPEN
MOVING DOWN ↓
DOOR OPEN
Back to IDLE
Java — Elevator LLD
enum Direction { UP, DOWN, IDLE }
enum ElevatorState { MOVING, DOOR_OPEN, IDLE }

class Elevator {
    private int id;
    private int currentFloor = 1;
    private Direction direction = Direction.IDLE;
    private ElevatorState state = ElevatorState.IDLE;
    private TreeSet<Integer> floorQueue = new TreeSet<>();

    public void addRequest(int floor) {
        floorQueue.add(floor);
        if (state == ElevatorState.IDLE) processRequests();
    }

    private void processRequests() {
        while (!floorQueue.isEmpty()) {
            int nextFloor;
            if (direction == Direction.UP || direction == Direction.IDLE) {
                // Get next floor above (SCAN algorithm)
                Integer above = floorQueue.ceiling(currentFloor);
                nextFloor = (above != null) ? above : floorQueue.first();
            } else {
                Integer below = floorQueue.floor(currentFloor);
                nextFloor = (below != null) ? below : floorQueue.last();
            }
            moveToFloor(nextFloor);
            floorQueue.remove(nextFloor);
        }
        direction = Direction.IDLE;
        state = ElevatorState.IDLE;
    }

    private void moveToFloor(int targetFloor) {
        direction = (targetFloor > currentFloor) ? Direction.UP : Direction.DOWN;
        state = ElevatorState.MOVING;
        currentFloor = targetFloor;
        // Open door, wait, close door
        state = ElevatorState.DOOR_OPEN;
    }

    public int getCurrentFloor() { return currentFloor; }
    public ElevatorState getState() { return state; }
}

// Dispatcher — assigns requests to optimal elevator
class ElevatorController {
    private List<Elevator> elevators;

    public void requestElevator(int floor) {
        // Find nearest idle or same-direction elevator
        Elevator best = elevators.stream()
            .filter(e -> e.getState() == ElevatorState.IDLE)
            .min(Comparator.comparingInt(
                e -> Math.abs(e.getCurrentFloor() - floor)))
            .orElse(elevators.get(0));

        best.addRequest(floor);
    }
}

Chess Game

🎯
Requirements
Two players, 8x8 board, 6 piece types. Each piece moves differently. Detect check, checkmate, stalemate. Support special moves (castling, en passant, promotion). Track game history.
Java — Chess LLD (Core Structure)
enum Color { WHITE, BLACK }
enum GameStatus { ACTIVE, CHECK, CHECKMATE, STALEMATE, RESIGN }

class Position {
    private int row, col;  // 0-7
    public Position(int r, int c) { row = r; col = c; }
    public boolean isValid() { return row >= 0 && row < 8 && col >= 0 && col < 8; }
}

// Abstract piece — polymorphism for move validation
abstract class Piece {
    protected Color color;
    protected Position position;
    protected boolean hasMoved = false;

    public abstract List<Position> getValidMoves(Board board);
    public abstract String getSymbol();
}

class King extends Piece {
    @Override
    public List<Position> getValidMoves(Board board) {
        List<Position> moves = new ArrayList<>();
        int[][] dirs = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
        for (int[] d : dirs) {
            Position p = new Position(position.row+d[0], position.col+d[1]);
            if (p.isValid() && !board.hasFriendlyPiece(p, color))
                moves.add(p);
        }
        return moves;
    }
    @Override public String getSymbol() { return color==Color.WHITE ? "♔" : "♚"; }
}

class Board {
    private Piece[][] grid = new Piece[8][8];

    public Piece getPiece(Position p) { return grid[p.row][p.col]; }

    public boolean hasFriendlyPiece(Position p, Color color) {
        Piece piece = getPiece(p);
        return piece != null && piece.color == color;
    }

    public boolean isKingInCheck(Color kingColor) {
        Position kingPos = findKing(kingColor);
        // Check if any enemy piece can reach kingPos
        for (int r = 0; r < 8; r++) {
            for (int c = 0; c < 8; c++) {
                Piece p = grid[r][c];
                if (p != null && p.color != kingColor) {
                    if (p.getValidMoves(this).contains(kingPos)) return true;
                }
            }
        }
        return false;
    }
}
Chapter 10

Interview Tips & Cheat Sheet

Everything you need to ace an LLD interview at any top tech company.

How to Approach an LLD Interview

1

Clarify Requirements (3-5 min)

Ask about scope: who are the users? What are the core features? What are we NOT building? Scale estimates? Always ask before coding.

2

Identify Core Entities (3-5 min)

List the nouns from requirements — these become your classes. E.g., for a hotel system: Hotel, Room, Guest, Booking, Payment.

3

Define Relationships (5 min)

Draw a quick class diagram on a whiteboard. Show IS-A, HAS-A relationships. Use correct UML notation.

4

Identify Design Patterns (2-3 min)

Which patterns apply? Singleton for config/managers, Observer for events, Strategy for algorithms, Factory for object creation.

5

Code Core Classes (15-20 min)

Start with interfaces and abstract classes. Write concrete implementations. Show enums. Demonstrate the main flow.

6

Discuss Extensions & Trade-offs

What would change if we needed X feature? How would you handle concurrency? What are the limitations of your design?

Pattern Recognition Cheat Sheet

When to Use Which Pattern
SituationPatternExample
Need only ONE instanceSingletonLogger, Config, DB Connection
Creating objects without specifying exact classFactoryNotifications, Documents, Parsers
Building complex objects step by stepBuilderSQL queries, HTTP requests, UI forms
Incompatible interfaces need to work togetherAdapterThird-party APIs, Legacy code
Add behavior dynamically without subclassingDecoratorStreams, Coffee toppings, Middleware
One event, many listenersObserverEvent systems, UI listeners, Pub/Sub
Interchangeable algorithmsStrategySorting, Payment, Navigation
Object changes behavior based on stateStateElevator, Vending machine, Order status
🏆
Golden Rules for Interviews
1. Always start with requirements. 2. Think in interfaces, not concrete classes. 3. Prefer composition over inheritance. 4. Name things clearly — good naming tells the design story. 5. Apply SOLID principles and mention them. 6. Speak your thought process aloud — interviewers evaluate your thinking, not just the code.

Common LLD Interview Questions

🔥 Most Asked
  • 🅿️ Design a Parking Lot
  • 🏢 Design an Elevator System
  • 📚 Design Library Management
  • ♟️ Design Chess / Snake & Ladder
  • 🏨 Design Hotel Booking System
  • 🛒 Design an E-commerce Cart
⭐ Advanced
  • 🍔 Design Splitwise / Bill Splitter
  • 🚗 Design Ride Sharing (Uber)
  • 🎬 Design Movie Ticket Booking
  • 💬 Design Chat Application
  • 🏦 Design ATM / Banking System
  • 📊 Design a Logging Framework