Pillar 6 — Low-Level Design (LLD)

Object-oriented design, design patterns, hands-on coding in Java
← Back to Tracker

SOLID & OOP Principles

Full-Focus
Tip: Be ready to refactor a class that violates SOLID principles live in the interview. Interviewers love giving a messy class and asking you to clean it up using SOLID.

SOLID

SRP — Single Class Responsibility #229

A class should have only one reason to change. SRP keeps classes focused, testable, and easy to maintain.

  • Identify the single axis of change a class owns
  • Extract responsibilities into collaborators when a class does too much
  • Common violation: a class that validates, persists, and formats output
  • Refactoring strategy: split by actor/stakeholder who requests changes

OCP — Open for Extension #230

Software entities should be open for extension but closed for modification. Add new behavior without editing existing code.

  • Use polymorphism or strategy injection to extend behavior
  • Avoid long if-else/switch chains that grow with every new feature
  • Java approach: define an interface, add new implementations
  • Real-world example: adding a new payment method without touching PaymentProcessor

LSP — Substitutability #231

Subtypes must be substitutable for their base types without altering program correctness. Violated when a subclass weakens postconditions or strengthens preconditions.

  • Classic violation: Square extending Rectangle and breaking setWidth/setHeight
  • Subtypes must honor the base type's behavioral contract
  • Throw UnsupportedOperationException = likely LSP violation
  • Design by contract: preconditions, postconditions, invariants

ISP — Interface Segregation #232

No client should be forced to depend on methods it does not use. Prefer many small, role-specific interfaces over one fat interface.

  • Fat interface anti-pattern: implementors throw UnsupportedOperationException for unused methods
  • Split interfaces by client role (e.g., Readable, Writable, Closeable)
  • Java 8+ default methods can help but don't abuse them to mask ISP violations
  • Interview signal: show awareness of interface granularity tradeoffs

DIP — Depend on Abstractions #233

High-level modules should not depend on low-level modules; both should depend on abstractions. The cornerstone of testable, decoupled architecture.

  • Inject dependencies via constructor, not via new keyword inside methods
  • Spring's @Autowired is DIP in action — know how it works under the hood
  • Enables mocking and unit testing of high-level business logic
  • Contrast with Service Locator pattern (also DIP-compliant but less explicit)

OOP Depth

Composition over Inheritance #234

Favor composing objects via has-a relationships over extending via is-a. Composition gives flexibility, avoids fragile base-class problems.

  • Inheritance creates tight coupling; changes to the parent cascade down
  • Delegation pattern: wrapper holds a reference and forwards calls
  • Java example: using a List field instead of extending ArrayList
  • Effective Java Item 18: prefer composition over inheritance

Encapsulation in Java #235

Bundling data with the methods that operate on it while restricting direct access to internal state. The foundation of information hiding.

  • Private fields + public getters/setters (but not blindly for every field)
  • Immutable objects: all fields final, no setters, defensive copies
  • Access modifiers hierarchy: private, default, protected, public
  • Encapsulation enables invariant enforcement inside the class

Polymorphism — Runtime vs Compile-time #236

Runtime polymorphism (method overriding / dynamic dispatch) vs compile-time polymorphism (method overloading). Know when and why each is used.

  • Runtime: JVM resolves the actual method at call time via vtable lookup
  • Compile-time: compiler picks the overload based on static types
  • Interview trap: overloading is NOT true polymorphism in the OO sense
  • Covariant return types in Java (overriding can narrow the return type)

Abstract Class vs Interface Tradeoffs #237

When to use an abstract class (shared state + partial implementation) vs an interface (pure contract, multiple inheritance of type).

  • Java 8+ interfaces can have default and static methods, blurring the line
  • Abstract class: when subclasses share state (fields) or constructor logic
  • Interface: when unrelated classes need to share a capability (Comparable, Serializable)
  • A class can implement many interfaces but extend only one abstract class
  • Interview pattern: explain when you would choose one over the other in a design

Design Patterns

Full-Focus

Creational Patterns

Singleton (Thread-safe — Double-checked Locking) #238

Intent: Ensure a class has exactly one instance and provide a global access point. When to use: Database connection pools, configuration managers, caches.

  • Double-checked locking with volatile field for thread safety
  • Bill Pugh approach: static inner holder class (lazy, thread-safe, no synchronization)
  • Enum singleton — Joshua Bloch's recommended approach
  • Java context: Spring beans are singletons by default (container-managed)
  • Pitfall: serialization can break singleton — implement readResolve()

Builder (Lombok vs Manual) #239

Intent: Separate construction of a complex object from its representation. When to use: Objects with many optional parameters, immutable objects with fluent APIs.

  • Manual builder: static inner Builder class with method chaining
  • Lombok @Builder: annotation-processor-generated builder at compile time
  • Ensures immutability — build() returns a fully constructed, unmodifiable object
  • Java context: StringBuilder, Stream.builder(), Protobuf builders all use this
  • Interview: know how to write a manual builder from scratch

Factory Method #240

Intent: Define an interface for creating an object but let subclasses decide which class to instantiate. When to use: When the creation logic varies by context or configuration.

  • Defers instantiation to subclasses via an abstract createXxx() method
  • Follows OCP: add new product types without changing existing factory code
  • Java context: Calendar.getInstance(), NumberFormat.getInstance()
  • Contrast with Simple Factory (static method, not a GoF pattern)

Abstract Factory #241

Intent: Provide an interface for creating families of related objects without specifying concrete classes. When to use: Cross-platform UI toolkits, themed component sets.

  • Factory of factories — groups related factory methods
  • Ensures product family consistency (e.g., all components from same theme)
  • Java context: DocumentBuilderFactory, TransformerFactory in javax.xml
  • More complex than Factory Method; use only when you have product families

Prototype #242

Intent: Create new objects by cloning an existing instance (prototype). When to use: When object creation is expensive and a similar object already exists.

  • Java's Cloneable interface and Object.clone() method
  • Shallow vs deep copy — critical distinction for nested objects
  • Copy constructor as a safer alternative to clone()
  • Java context: useful in game dev (cloning enemy templates), config presets

Structural Patterns

Decorator #243

Intent: Attach additional responsibilities to an object dynamically, providing a flexible alternative to subclassing. When to use: Wrapping I/O streams, adding logging/caching layers.

  • Wraps the original object; both share the same interface
  • Java context: BufferedInputStream wrapping FileInputStream
  • Can stack decorators: compress(encrypt(log(stream)))
  • Follows OCP — add behavior without modifying existing classes

Adapter #244

Intent: Convert the interface of a class into another interface that clients expect. When to use: Integrating legacy code, third-party libraries with incompatible interfaces.

  • Class adapter (via inheritance) vs object adapter (via composition)
  • Java context: Arrays.asList() adapts an array to a List interface
  • InputStreamReader adapts byte stream to character stream
  • Very common in interview LLD — know how to sketch one quickly

Proxy (JDK Dynamic Proxy) #245

Intent: Provide a surrogate or placeholder for another object to control access. When to use: Lazy loading, access control, logging, AOP interceptors.

  • JDK dynamic proxy: java.lang.reflect.Proxy + InvocationHandler
  • CGLIB proxy: subclass-based, used by Spring for non-interface beans
  • Spring AOP uses proxies for @Transactional, @Cacheable, etc.
  • Types: virtual proxy (lazy), protection proxy (access control), remote proxy (RPC)

Facade #246

Intent: Provide a unified, simplified interface to a set of interfaces in a subsystem. When to use: Hiding complex subsystem interactions behind a clean API.

  • Reduces coupling between clients and subsystem internals
  • Java context: SLF4J is a facade over various logging frameworks
  • Spring's JdbcTemplate is a facade over raw JDBC boilerplate
  • Not the same as Adapter (adapter changes interface; facade simplifies)

Composite #247

Intent: Compose objects into tree structures to represent part-whole hierarchies. Clients treat individual objects and compositions uniformly. When to use: File systems, UI component trees, organizational hierarchies.

  • Component interface with Leaf and Composite implementations
  • Composite holds a collection of children (both leaves and composites)
  • Java context: java.awt.Container (contains Components, which can be Containers)
  • Recursive structure — operations propagate through the tree

Flyweight #248

Intent: Use sharing to support large numbers of fine-grained objects efficiently. When to use: When many objects share intrinsic state and differ only in extrinsic state.

  • Separate intrinsic (shared, immutable) state from extrinsic (context-dependent) state
  • Flyweight factory manages a pool/cache of shared instances
  • Java context: Integer.valueOf() caches -128 to 127 (Integer pool)
  • String interning in Java is a flyweight implementation

Behavioral Patterns

Strategy #249

Intent: Define a family of algorithms, encapsulate each one, and make them interchangeable at runtime. When to use: Payment processing, sorting strategies, pricing rules.

  • Context holds a reference to a Strategy interface; swaps implementations
  • Eliminates conditional logic — replaces if-else chains with polymorphism
  • Java context: Comparator is a strategy for Collections.sort()
  • Lambda-friendly in Java 8+ — pass behavior as a functional interface
  • Closely related to OCP — add new strategies without modifying existing code

Observer #250

Intent: Define a one-to-many dependency so that when one object changes state, all dependents are notified. When to use: Event systems, pub-sub, UI listeners.

  • Subject maintains a list of observers and notifies them on state change
  • Push vs pull model — push sends data; pull lets observers query
  • Java context: java.util.Observer (deprecated) and PropertyChangeListener
  • Foundation of event-driven architectures and reactive programming

Command #251

Intent: Encapsulate a request as an object, allowing parameterization, queuing, logging, and undo/redo operations. When to use: Task queues, macro recording, transactional systems.

  • Command object encapsulates receiver + action + parameters
  • Invoker triggers execute(); knows nothing about the receiver
  • Enables undo by storing command history and calling unexecute()
  • Java context: Runnable is a simple command pattern (no undo)

Chain of Responsibility #252

Intent: Pass a request along a chain of handlers until one handles it. Decouples sender from receiver. When to use: Servlet filters, logging frameworks, approval workflows.

  • Each handler decides to process or pass to the next handler
  • Java context: javax.servlet.Filter chain, Spring Security filter chain
  • Can also allow all handlers to process (interceptor/pipeline variant)
  • Order of handlers matters — configure chain carefully

State Machine #253

Intent: Allow an object to alter its behavior when its internal state changes. The object appears to change its class. When to use: Order lifecycle, vending machines, document workflows.

  • Context delegates behavior to the current State object
  • Each state class encapsulates transitions to other states
  • Eliminates complex switch/if-else state management code
  • Java context: Spring State Machine library; order status transitions
  • Common interview LLD problem: vending machine uses State pattern internally

Template Method #254

Intent: Define the skeleton of an algorithm in a superclass, letting subclasses override specific steps. When to use: Frameworks, data parsers, test fixtures with setup/teardown.

  • Abstract class defines the template method (final) calling abstract steps
  • Subclasses implement the variable steps; skeleton stays unchanged
  • Hollywood Principle: "Don't call us, we'll call you"
  • Java context: HttpServlet's doGet/doPost, JUnit's setUp/tearDown

Iterator #255

Intent: Provide a way to access elements of a collection sequentially without exposing its underlying representation. When to use: Custom collections, tree traversals, streaming data.

  • Java's Iterable / Iterator interfaces are the canonical implementation
  • Enhanced for-loop (for-each) uses Iterator under the hood
  • Fail-fast iterators throw ConcurrentModificationException
  • Java context: custom Iterator for a linked list or tree structure

LLD Practice Problems

Full-Focus
Tip: Code these fully in Java — not just class diagrams. Focus on extensibility, not just correctness. Interviewers evaluate your use of SOLID, patterns, and clean separation of concerns.

LLD Tier 1 — Must Code

Parking Lot System #256

Design a multi-floor parking lot with different vehicle types, entry/exit gates, and billing. One of the most frequently asked LLD problems.

  • Key classes: ParkingLot, Floor, ParkingSpot (Compact/Large/Handicapped), Vehicle, Ticket, EntryGate, ExitGate
  • Design challenge: Spot allocation strategy (nearest to entry, per floor), vehicle-to-spot mapping
  • Use Strategy pattern for spot assignment; Observer for availability updates
  • Handle concurrent entry/exit with thread-safe spot reservation
  • Pricing: hourly rates, flat rates, per-vehicle-type — use Strategy pattern

Elevator / Lift System #257

Design an elevator control system for a building with multiple elevators. Tests your ability to model state transitions and scheduling algorithms.

  • Key classes: Building, ElevatorController, Elevator, Request (internal/external), Door, Display
  • Design challenge: Elevator scheduling algorithm (SCAN, LOOK, shortest-seek-first)
  • Use State pattern for elevator states: IDLE, MOVING_UP, MOVING_DOWN, DOOR_OPEN
  • Strategy pattern for the dispatching algorithm
  • Handle edge cases: overweight, emergency stop, maintenance mode

Library Management #258

Design a library system that manages books, members, borrowing, returns, and fines. Focuses on clean entity modeling and business rules.

  • Key classes: Library, Book, BookItem (physical copy), Member, Librarian, Loan, Fine, Catalog
  • Design challenge: Distinguishing Book (metadata) from BookItem (physical copy); reservation and waitlist logic
  • Search catalog by title, author, ISBN, subject — use Catalog with search strategies
  • Fine calculation: per-day overdue, max cap, waiver rules
  • Notification system for due dates (Observer pattern)

Chess Game #259

Design a two-player chess game. Tests inheritance hierarchies, move validation, and game state management.

  • Key classes: Game, Board, Cell, Piece (King/Queen/Rook/Bishop/Knight/Pawn), Player, Move
  • Design challenge: Move validation per piece type; check/checkmate detection
  • Piece hierarchy with abstract isValidMove(Board, source, dest) method
  • Special moves: castling, en passant, pawn promotion
  • Game state: ACTIVE, CHECK, CHECKMATE, STALEMATE, RESIGNED

Snake and Ladder #260

Design a Snake and Ladder board game for multiple players. Simpler than chess but tests clean game-loop modeling.

  • Key classes: Game, Board, Cell, Snake, Ladder, Player, Dice
  • Design challenge: Board initialization with snakes/ladders; turn-based game loop
  • Dice can be configurable (number of dice, faces per die)
  • Handle edge cases: snake at 99, multiple snakes/ladders in sequence
  • Easy to extend: power-ups, mines, multiplayer variations

ATM Machine #261

Design an ATM system supporting withdrawal, deposit, balance inquiry, and PIN verification. Classic State pattern application.

  • Key classes: ATM, ATMState (Idle/CardInserted/PINValidation/Transaction), CashDispenser, Account, Card, Transaction
  • Design challenge: State transitions and cash denomination dispensing (greedy algorithm)
  • State pattern for ATM states; Chain of Responsibility for cash dispensing
  • Handle: insufficient funds, card retained, daily withdrawal limits
  • Transaction types: Withdrawal, Deposit, BalanceInquiry, Transfer

Tic-tac-toe / Connect Four #262

Design a generalized board game (NxN tic-tac-toe or Connect Four). Tests clean abstractions and win-condition checking.

  • Key classes: Game, Board, Cell, Player, Piece, WinConditionChecker
  • Design challenge: Generalizing board size (NxN) and win-condition (K in a row)
  • Strategy pattern for win-checking algorithms (row, column, diagonal scans)
  • Undo support via Command pattern (optional but impressive)
  • Extensible to AI player: plug in a MoveStrategy (Minimax, random)

LLD Tier 2 — Important

Hotel Management #263

Design a hotel booking and management system. Tests reservation workflows, room allocation, and billing.

  • Key classes: Hotel, Room (Single/Double/Suite), Reservation, Guest, Payment, HouseKeeping, RoomKey
  • Design challenge: Room availability calendar, overbooking strategy, check-in/check-out flow
  • Search rooms by date range, type, floor preference
  • State pattern for room status: AVAILABLE, RESERVED, OCCUPIED, MAINTENANCE
  • Observer for housekeeping notifications on checkout

Ride-sharing LLD (Ola/Uber) #264

Design a ride-sharing service with riders, drivers, matching, trip lifecycle, and pricing. A complex, real-world system design scaled down to LLD.

  • Key classes: Rider, Driver, Trip, Location, RideRequest, PricingStrategy, DriverMatchingStrategy, Payment
  • Design challenge: Driver-rider matching algorithm and pricing strategy (surge, flat, distance-based)
  • Strategy for matching: nearest driver, highest rated, ride-type preference
  • Trip states: REQUESTED, DRIVER_ASSIGNED, IN_PROGRESS, COMPLETED, CANCELLED
  • Observer pattern to notify rider of driver location updates

Movie Ticket Booking (BookMyShow) #265

Design an online movie ticket booking system with theaters, shows, seat selection, and payment. Tests concurrency and seat locking.

  • Key classes: Theater, Screen, Show, Movie, Seat (Premium/Regular), Booking, Payment, SeatLock
  • Design challenge: Concurrent seat selection — temporary seat locks with TTL to prevent double booking
  • Search: movies by city, shows by movie, available seats by show
  • Seat lock mechanism: optimistic locking or distributed lock with expiry
  • Pricing: base + seat-type premium + time-of-day surcharge

Food Delivery (Swiggy LLD) #266

Design a food delivery platform with restaurants, menus, orders, delivery assignment, and tracking. Multi-entity coordination challenge.

  • Key classes: Restaurant, Menu, MenuItem, Cart, Order, DeliveryAgent, Customer, Payment, OrderTracker
  • Design challenge: Order lifecycle management, delivery agent assignment, ETA estimation
  • Strategy for agent assignment: nearest, least-loaded, cuisine-expertise
  • Observer for real-time order status updates to customer
  • Order states: PLACED, CONFIRMED, PREPARING, OUT_FOR_DELIVERY, DELIVERED, CANCELLED

Splitwise #267

Design an expense-sharing app with groups, expenses, split strategies, and balance settlement. Tests algorithmic thinking within LLD.

  • Key classes: User, Group, Expense, Split (Equal/Exact/Percentage), BalanceSheet, Transaction
  • Design challenge: Multiple split strategies and minimum-transaction settlement algorithm
  • Strategy pattern for split types: EqualSplit, ExactSplit, PercentageSplit
  • Balance simplification: minimize number of transactions using net-balance approach
  • Handle: group expenses, 1-on-1 expenses, partial settlements

Vending Machine #268

Design a vending machine that accepts coins/notes, dispenses products, and returns change. The canonical State pattern problem.

  • Key classes: VendingMachine, State (Idle/HasMoney/Dispensing/OutOfStock), Product, Inventory, Coin, CashRegister
  • Design challenge: State transitions and change-making algorithm (coin denomination selection)
  • State pattern is the backbone: each state handles insertCoin, selectProduct, dispense differently
  • Chain of Responsibility for change dispensing by denomination
  • Handle: insufficient funds, out of stock, exact change only mode

Online Auction #269

Design an online auction system with listings, bidding, auto-bid, and winner determination. Tests event-driven modeling and timing.

  • Key classes: Auction, Item, Bid, User (Seller/Bidder), AuctionTimer, AutoBidRule, Notification
  • Design challenge: Concurrent bidding, auto-bid logic, auction end timing
  • Observer for bid notifications to all watchers of an auction
  • Strategy for auction types: English (ascending), Dutch (descending), sealed-bid
  • Handle: reserve price, bid increment rules, snipe protection (time extension)

LLD Tier 3 — Good to Have

Stock Exchange Order Matching #270

Design an order matching engine for a stock exchange. Tests data-structure selection and concurrent order processing.

  • Key classes: OrderBook, Order (Buy/Sell/Limit/Market), Trade, Stock, Trader, MatchingEngine
  • Design challenge: Price-time priority matching using sorted order books (TreeMap or priority queue)
  • Matching algorithm: best bid meets best ask; partial fills
  • Thread-safe order book with concurrent read/write access
  • Handle: market orders, limit orders, cancel/modify, partial execution

Traffic Light Controller #271

Design a traffic light controller for a multi-way intersection. Tests state machines and timing coordination.

  • Key classes: Intersection, TrafficLight, LightState (RED/YELLOW/GREEN), Controller, Timer, EmergencyOverride
  • Design challenge: Coordinating multiple lights so conflicting directions are never green simultaneously
  • State pattern for each light; Controller manages the phase sequence
  • Handle: pedestrian crossing signals, emergency vehicle preemption, sensor-triggered phases
  • Configurable phase durations per intersection

Log Parser System #272

Design a configurable log parsing and aggregation system. Tests Chain of Responsibility and extensible architecture.

  • Key classes: LogParser, LogEntry, Filter, Aggregator, Alert, Sink (Console/File/DB), Pipeline
  • Design challenge: Pluggable pipeline of parsers, filters, and sinks without modifying core engine
  • Chain of Responsibility for filter pipeline (severity, regex, time-range)
  • Strategy for output sinks: console, file, database, alerting
  • Builder pattern for constructing complex pipeline configurations

Concurrency in LLD

Full-Focus

Concurrency Patterns

Producer-Consumer with BlockingQueue #273

The foundational concurrency pattern where producers add items to a shared buffer and consumers remove them. BlockingQueue in Java handles synchronization internally.

  • Use ArrayBlockingQueue (bounded) or LinkedBlockingQueue (optionally bounded)
  • put() blocks when full; take() blocks when empty — no manual wait/notify needed
  • Common in thread pool internals, message processing, and event-driven systems
  • Know how to implement a bounded buffer with wait/notify from scratch
  • Java context: ThreadPoolExecutor uses a BlockingQueue as its work queue

Reader-Writer Lock Pattern #274

Allows multiple concurrent readers but exclusive access for writers. Optimizes throughput when reads vastly outnumber writes.

  • Java's ReentrantReadWriteLock: readLock() for shared, writeLock() for exclusive
  • Write-starving risk: many readers can indefinitely delay a waiting writer
  • Fair vs unfair modes — fair mode prevents starvation but reduces throughput
  • StampedLock (Java 8+): optimistic read locks for even better read performance
  • Use case: caches, configuration stores, in-memory databases

Thread-safe Singleton Variants #275

Multiple approaches to making Singleton thread-safe in Java, each with different performance and laziness characteristics.

  • Eager initialization: static final field, simplest but not lazy
  • Synchronized getInstance(): lazy but high contention on every call
  • Double-checked locking: volatile + sync block, lazy with low contention
  • Bill Pugh (static inner class holder): lazy, thread-safe, no synchronization overhead
  • Enum singleton: immune to serialization and reflection attacks

Semaphore-based Rate Limiter #276

Using Semaphore to limit concurrent access to a resource. A practical concurrency primitive for rate limiting and connection pooling.

  • Semaphore(permits): acquire() decrements, release() increments
  • tryAcquire(timeout) for non-blocking rate limiting with fallback
  • Token bucket / leaky bucket algorithms often use Semaphore internally
  • Java context: limiting concurrent database connections or API calls
  • Compare with: RateLimiter from Guava (token-bucket, time-based)

ExecutorService Task Dispatch #277

Using Java's ExecutorService framework to manage thread pools and dispatch tasks. The standard way to handle concurrency in production Java.

  • Executors.newFixedThreadPool(), newCachedThreadPool(), newScheduledThreadPool()
  • submit() returns Future; invokeAll() for batch execution
  • CompletableFuture for composable async pipelines (thenApply, thenCompose, allOf)
  • Graceful shutdown: shutdown() + awaitTermination() vs shutdownNow()
  • Custom ThreadPoolExecutor: core size, max size, queue type, rejection policy

Recommended Resources

Refactoring Guru — Design Patterns

Visual catalog of all GoF patterns with UML diagrams, real-world analogies, and code examples in multiple languages.

Baeldung — Java Tutorials

Comprehensive Java tutorials covering OOP, design patterns, concurrency, Spring, and more with production-grade examples.

SourceMaking — Design Patterns

Pattern catalog with intent, structure, participants, and consequences. Great for quick revision before interviews.

Oracle Java Tutorials

Official Java tutorials covering language fundamentals, OOP, generics, concurrency, and the Collections framework.

Refactoring Guru — Refactoring

Code smells and refactoring techniques. Essential for SOLID principle application and writing clean, maintainable code.

Baeldung — Java Concurrency

Deep dives into threads, locks, executors, CompletableFuture, and concurrent data structures in Java.