Low Level Design (LLD)
In a Nutshell
If High Level Design is about which boxes to draw, Low Level Design is about what's inside the hardest box. LLD zooms into a single component from your HLD and works through its internals: the classes and interfaces, the database schema, the algorithms, and the edge cases. It's where you demonstrate that you can go from "we need a URL shortening service" to "here's the class structure, the key generation algorithm, the schema, and how we handle collisions."

How It Actually Works
What LLD Includes
| Element | What You Design | Example |
|---|---|---|
| Classes & Interfaces | The objects, their responsibilities, and how they interact | URLService, URLRepository, KeyGenerator |
| Database Schema | Tables, columns, types, primary keys, foreign keys, indexes | urls(id, short_code, long_url, created_at, expires_at) |
| API Contracts | Endpoint signatures, request/response shapes | POST /urls { long_url, custom_alias? } → { short_url } |
| Algorithms | The logic for the hard part | Base62 encoding of an auto-increment ID, or MD5 hash with collision handling |
| Edge Cases | What happens when things go wrong | Duplicate URLs, expired links, invalid input, race conditions |
| Design Patterns | Named solutions to recurring problems | Factory for key generation strategy, Strategy for different encoding schemes |
When to Do LLD
In an interview:
- After you've drawn the HLD and walked through the request paths
- When the interviewer says "Let's go deeper on the X service"
- When you choose to go deep on the component you know is hardest — this shows initiative
In real-world design docs:
- After the architecture review, when the team assigned to a specific service writes their detailed design
- When the component is complex enough that implementation will be ambiguous without upfront design
The LLD Process
- Identify the core entities — What are the nouns? (User, URL, Click, Order, Message)
- Define relationships — How do entities relate? (A User creates many URLs. A URL has many Clicks.)
- Design the API — What operations are needed? Map to REST endpoints or gRPC methods.
- Design the schema — Tables, columns, indexes. Think about query patterns — what queries will be hot?
- Design the algorithm — What's the hard logic? (Key generation, feed ranking, matching, conflict resolution)
- Apply design patterns — Where do Singleton, Factory, Strategy, Observer, or Repository patterns help?
- Handle edge cases — What happens on duplicate input? Concurrent writes? Network failures? Invalid state transitions?
SOLID Principles in LLD
| Principle | What It Means | LLD Application |
|---|---|---|
| Single Responsibility | A class does one thing | KeyGenerator only generates keys; URLRepository only handles persistence |
| Open/Closed | Open for extension, closed for modification | Add a new key generation strategy without changing existing ones |
| Liskov Substitution | Subtypes must be substitutable for their base type | Any KeyGenerator implementation can be swapped in without breaking callers |
| Interface Segregation | Don't force clients to depend on methods they don't use | Separate ReadableURLStore and WritableURLStore interfaces |
| Dependency Inversion | Depend on abstractions, not concretions | URLService depends on KeyGenerator interface, not Base62KeyGenerator directly |

Seeing It in Action
Scenario: LLD for the URL Shortening Service from the HLD
Step 1 — Core entities:
URL— the mapping between a short code and a long URLClick— a record of each redirect (for analytics)
Step 2 — API design:
POST /api/v1/urls → Create short URL
GET /api/v1/urls/{code} → Get URL details
GET /{code} → Redirect (302)
DELETE /api/v1/urls/{code} → Delete short URL
Step 3 — Database schema:
CREATE TABLE urls (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
short_code VARCHAR(8) UNIQUE NOT NULL,
long_url TEXT NOT NULL,
user_id BIGINT,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
click_count BIGINT DEFAULT 0,
INDEX idx_short_code (short_code)
);
Step 4 — Key generation algorithm:
Option A: Counter + Base62
- Auto-increment ID → convert to base62 → "abc123"
- Pros: No collisions, simple
- Cons: Predictable (sequential), single point of counter
Option B: Hash (MD5/SHA256) + truncate
- MD5(long_url) → take first 7 chars
- Pros: Same URL always gets same code
- Cons: Collisions possible → need collision handling loop
Option C: Pre-generated key pool
- Offline worker pre-generates millions of unique codes
- Service pulls from pool on demand
- Pros: No collision at runtime, fast
- Cons: Operational complexity of the key pool
Step 5 — Class design:
URLService
├── createShortURL(longURL, customAlias?) → ShortURL
├── redirect(shortCode) → LongURL
└── deleteURL(shortCode) → void
KeyGenerator (interface)
├── Base62KeyGenerator (implements KeyGenerator)
└── HashKeyGenerator (implements KeyGenerator)
URLRepository (interface)
├── save(url: URL) → void
├── findByShortCode(code: String) → URL?
└── delete(code: String) → void
Interview Questions
Q: How do you decide which component to do LLD on in a system design interview? Hint: Pick the one that's hardest or most unique to this system — the fan-out logic in a news feed, the matching algorithm in a ride-sharing app, the idempotency layer in a payment system. Avoid doing LLD on generic components like auth or logging unless specifically asked.
Q: Design the class structure for a notification service that supports email, SMS, and push notifications. Hint: Use the Strategy pattern:
NotificationServicetakes aNotificationSenderinterface. ImplementEmailSender,SMSSender,PushSender. Use a Factory to select the right sender based on user preference. This lets you add new channels without modifying existing code (Open/Closed principle).Q: How would you design the database schema for a chat application? What indexes would you add? Hint: Tables:
users,conversations,messages(id, conversation_id, sender_id, content, created_at). Index on(conversation_id, created_at)for fetching messages in a conversation chronologically. Partition byconversation_idif scale requires it. Consider whether to store read receipts as a separate table or a column.Q: What's the difference between a class diagram and an ER diagram, and when would you use each? Hint: Class diagram shows objects, methods, and OOP relationships (inheritance, composition) — used in LLD for service internals. ER diagram shows database tables, columns, and data relationships (one-to-many, many-to-many) — used for persistent data modeling. They're related but serve different purposes.
Q: You're designing a key generation algorithm that must produce unique, non-predictable short codes at 10K writes/sec. What approach do you take? Hint: Pre-generated key pool with randomized codes. A background worker generates batches of random base62 strings, checks uniqueness, and stores them. The main service pulls from the pool — no collision at request time, no predictability, and the pool can be distributed across multiple app servers by assigning ranges.
References
- System Design Interview – An Insider's Guide Vol. 2 by Alex Xu — detailed LLD examples for complex services
- Head First Design Patterns by Freeman & Robson — accessible introduction to design patterns with examples
- Clean Architecture by Robert C. Martin — SOLID principles and how to structure code for maintainability
Dive Deeper
- Design Patterns: Elements of Reusable Object-Oriented Software by Gang of Four — the original design patterns reference
- Refactoring Guru — Design Patterns — visual, interactive guide to every major pattern
- Low Level Design Problems — curated list of LLD interview problems with solutions