Background
Sections
IntroductionRequirements & Problem AnalysisConstraints & AssumptionsEstimation TechniquesFunctional vs Non-Functional RequirementsMoSCoW PrioritizationSystem Design FundamentalsArchitecture DiagramClass DiagramComponent DiagramData Flow Diagram (DFD)ER Diagram (Entity-Relationship Diagram)High Level Design (HLD)Low Level Design (LLD)Sequence DiagramState DiagramUse Case DiagramData StorageDocument StoresFile StorageGraph DatabasesIn-Memory DatabasesKey-Value StoresNewSQLNoSQL DatabasesObject StorageSQL Databases (RDBMS)Time-Series DatabasesWide-Column StoresDatabase ConceptsACID PropertiesCAP TheoremConsistency ModelsIndexingNormalization & DenormalizationReplicationSharding & PartitioningTransactions & Isolation LevelsScalabilityAuto-Scaling & ElasticityConsensus & Leader ElectionLoad BalancingReplication & Read ReplicasSharding & PartitioningVertical vs Horizontal ScalingAvailability & ReliabilityBackup & Data DurabilityCircuit BreakerData ConsistencyDisaster RecoveryFault Tolerance & FailoverGraceful DegradationHigh AvailabilityNetworkingCDNDNSFirewalls & VPNHTTP & HTTPSLoad Balancer & Reverse ProxyTCP/IP & UDPWebSocketsCachingCache InvalidationCache Read/Write PatternsCaching LayersEviction PoliciesRedis vs MemcachedMessaging & CommunicationDead-Letter QueueIdempotencyKafka vs RabbitMQ vs SQSMessage QueuesPub/SubCompute & ServicesAPI GatewayContainers & OrchestrationMonolith vs MicroservicesServerlessService DiscoveryService MeshWeb Server & App ServerAPI DesignAPI Versioning & IdempotencyAuthentication & AuthorizationGraphQLgRPCPaginationRate Limiting & ThrottlingRESTSecurityAuthentication & AuthorizationData PrivacyEncryptionInput Validation & InjectionOAuth2 & JWTSecrets ManagementXSS & CSRFStorage & File SystemsBackup & RetentionBlock vs File vs Object StorageData Lakes & WarehousesDistributed File SystemsEphemeral StorageObservability & MonitoringDistributed TracingHealth ChecksLoggingMetricsSLI, SLO, SLADesign PatternsBulkhead PatternCircuit Breaker PatternCreational PatternsRate Limiter PatternRetry PatternStructural & Behavioral Patterns

Low Level Design (LLD)

7 min read

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."

2D minimalistic diagram showing an HLD with one box highlighted and zoomed into, revealing internal structure with classes, a database schema table, and algorithm flowchart inside

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

  1. Identify the core entities — What are the nouns? (User, URL, Click, Order, Message)
  2. Define relationships — How do entities relate? (A User creates many URLs. A URL has many Clicks.)
  3. Design the API — What operations are needed? Map to REST endpoints or gRPC methods.
  4. Design the schema — Tables, columns, indexes. Think about query patterns — what queries will be hot?
  5. Design the algorithm — What's the hard logic? (Key generation, feed ranking, matching, conflict resolution)
  6. Apply design patterns — Where do Singleton, Factory, Strategy, Observer, or Repository patterns help?
  7. 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

2D minimalistic diagram showing SOLID principles as five connected blocks, each with a one-word label (Single, Open, Liskov, Interface, Dependency) and a simple icon representing the concept

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 URL
  • Click — 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

  1. 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.

  2. Q: Design the class structure for a notification service that supports email, SMS, and push notifications. Hint: Use the Strategy pattern: NotificationService takes a NotificationSender interface. Implement EmailSender, 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).

  3. 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 by conversation_id if scale requires it. Consider whether to store read receipts as a separate table or a column.

  4. 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.

  5. 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