System Design β€” LLD & Component Design

System Design (LLD)

Core concepts, architecture patterns, resilience, caching, databases, and component-level interview questions. For big-infra HLD questions, see High-Level Design β†’

Typical Scalable Web Architecture
Mobile App
Web Browser
3rd Party API
↓
CDNStatic Assets
DNS / AnycastGlobal Routing
↓
Load BalancerL7 β€” NGINX / ALB
↓
API GatewayAuth Β· Rate Limit Β· Route Β· Log
↓
User Service
Order Service
Product Service
Notif Service
↓
KafkaAsync Messaging
Redis ClusterCache Β· Sessions
↓
MySQL / PostgresACID Transactions
CassandraWrite-Heavy
S3Blob Storage
↓ async
Background WorkersJobs Β· ETL Β· Emails
AnalyticsSnowflake Β· BigQuery
Core Fundamentals

CAP Theorem

Pick any 2: Consistency Availability Partition Tolerance

  • Consistency Every read returns the most recent write
  • Availability Every request gets a non-error response
  • Partition Tolerance System works despite network partitions
SystemType
PostgreSQL, MySQLCA (single node)
Cassandra, DynamoDBAP (eventual)
HBase, Zookeeper, etcdCP (may reject)
MongoDB (default)CP (tunable)
CouchbaseAP or CP (configurable)
Reality
P is always required in distributed systems. The real choice is C vs A during a partition.

ACID vs BASE

ACID (SQL)BASE (NoSQL)
Atomicity all or nothingBasically Available
Consistency valid state alwaysSoft State
Isolation no interferenceEventually Consistent
Durability committed = safePrioritizes availability
Use ACID: banking, bookings, inventory. Use BASE: social feeds, analytics, caching, data catalog metadata.

Scalability Vertical vs Horizontal + Strategies

Vertical Scaling (Scale Up)
  • Add CPU/RAM to existing server
  • Simple no application changes
  • Single point of failure
  • Hard hardware limit, expensive
Horizontal Scaling (Scale Out)
  • Add more commodity machines
  • Theoretically unlimited
  • Requires stateless services + LB
  • Complex: distributed state, consistency

Key Scaling Strategies

  • Stateless Services: Store state in Redis/DB. Any instance handles any request.
  • Read Replicas: Primary for writes, replicas for reads (80% of traffic is reads)
  • Sharding: Partition data across nodes by shard key
  • CDN: Cache static assets globally, reduce origin server load drastically
  • Async Processing: Queue slow work (emails, reports) instant API response
  • Denormalization: Pre-compute joins faster reads at cost of storage + write complexity

PACELC Theorem (CAP Extension)

If (P)artition: choose (A) or (C). Else (E): choose (L)atency or (C)onsistency

  • CAP only models partition scenarios. PACELC says: even normally, there is a latency vs consistency tradeoff.
  • Replication = lower latency OR stronger consistency never both.
SystemPartition choiceElse choice
DynamoDBAvailabilityLatency (low)
CassandraAvailabilityLatency (low)
Google SpannerConsistencyConsistency (strong)
MySQLConsistencyConsistency
Databases

Database Indexing B-Tree, Hash, Composite

Must Know
TypeStructureBest ForNot For
B-TreeBalanced treeRange queries, ORDER BY, <, >, BETWEENHigh-cardinality exact lookups (hash faster)
Hash IndexHash tableExact equality (=), O(1)Range queries, sorting
CompositeB-Tree on N colsMulti-column queries (leftmost prefix rule)Queries not starting with leftmost column
Covering IndexIndex has all queried colsSatisfy query from index alone (no heap fetch)Wide tables with many columns
Full-TextInverted indexLIKE '%word%', text searchExact match (regular index faster)
Partial IndexIndex with WHERE clauseWHERE status='active' index only active rowsWhen all rows need indexing

Index Trade-offs

  • Reads faster: SELECT queries benefit dramatically (O(log n) vs O(n))
  • Writes slower: every INSERT/UPDATE/DELETE updates all indexes
  • Storage cost: indexes use ~20-30% extra disk space each
  • Cardinality: index on gender (2 values) = nearly useless; index on user_id = excellent
Leftmost Prefix Rule
Composite index (a, b, c) helps queries on: a (a,b) (a,b,c). Does NOT help: (b) (c) (b,c) alone.

SQL vs NoSQL When to Use What

FactorSQL (Relational)NoSQL
SchemaFixed, enforced (DDL)Flexible, schema-on-read
RelationshipsForeign keys, JOINsDenormalized, embedded
Horizontal ScalingDifficult (sharding complex)Native (Cassandra, Mongo)
TransactionsFull ACIDLimited / eventual
Query LanguageSQL (very expressive)API-specific, limited joins

NoSQL Types & Use Cases

  • Key-Value (Redis, DynamoDB): sessions, cache, feature flags
  • Document (MongoDB): product catalogs, CMS, user profiles
  • Wide-Column (Cassandra, HBase): IoT, time-series, write-heavy analytics
  • Graph (Neo4j): social network, knowledge graph, data lineage
  • Search (Elasticsearch): full-text search, log analytics
  • Time-Series (InfluxDB, TimescaleDB): metrics, monitoring

Choose SQL When

  • Complex queries with multiple JOINs
  • ACID transactions are critical (money, inventory)
  • Schema is stable and well-defined
  • Rich aggregation / reporting needs
  • E.g.: banking, ERP, booking, HR systems
  • Choose NoSQL when schema evolves rapidly or write throughput is extreme

Database Replication Strategies

Database Replication β€” Single Leader (Primary-Replica)
App Server 1
App Server 2
App Server 3
↓ WRITES only
↓ READS (scale-out)
PrimaryAll writes go here
β†’ sync / async β†’
Replica 1reads
Replica 2reads
Replica 3reads
Single Leader (Primary-Replica): Leader (writes) async/sync replication Replica 1, 2, 3 Reads can go to any replica (scale reads 3x) Multi-Leader: Leader A (DC West) replication Leader B (DC East) Both accept writes; conflict resolution required Leaderless (Dynamo-style): Client writes to N nodes; quorum W > N/2 Client reads from R nodes; R + W > N = consistency
StrategyProsCons
Single LeaderSimple, no conflicts, ordered writesLeader bottleneck, failover needed
Multi-LeaderWrite availability in multiple DCsConflict resolution required
LeaderlessHigh availability, no SPOFComplex quorums, eventual consistency

Sync vs Async Replication

  • Synchronous: write confirmed only when all replicas ack safe but slow
  • Asynchronous: write confirmed on leader write fast but potential data loss on leader crash
  • Semi-sync: at least 1 replica acks balance of safety and speed (MySQL default)

Database Sharding Strategies & Pitfalls

StrategyHowProsCons
Range ShardingA-M shard1, N-Z shard2Simple range queriesHotspots (all celebrities on one shard)
Hash Shardinghash(key) % NEven distributionRebalancing when N changes remaps many keys
Consistent HashingHash ring + virtual nodesMinimal remapping on add/removeMore complex, virtual nodes needed for uniformity
Directory ShardingLookup table: key shardFlexible, can move dataLookup service = SPOF, extra hop
Geo ShardingUS shard1, EU shard2Data locality, compliance (GDPR)Cross-region queries expensive
Sharding Pitfalls
Cross-shard JOINs = scatter-gather (expensive) Distributed transactions across shards need 2PC Rebalancing requires data migration Choose shard key carefully hard to change later Hot shard = one shard receives disproportionate traffic
Caching

Caching Strategies All Patterns

Critical
StrategyFlowUse WhenDownside
Cache-Aside (Lazy)Check cache miss load DB store in cacheRead-heavy, tolerate some stale dataCache miss penalty, thundering herd on cold start
Read-ThroughCache handles DB fetch transparently on missSimplified app code, uniform read pathCache must understand data model
Write-ThroughWrite to cache AND DB synchronouslyStrong consistency neededWrite latency doubled
Write-Behind (Write-Back)Write to cache; async flush to DBWrite-heavy, latency-criticalData loss if cache crashes before flush
Write-AroundWrite directly to DB, skip cacheWrite-once, read-rarely data (logs)Cache miss on first read after write
Refresh-AheadProactively refresh cache before TTL expiresPredictable, hot data access patternsWasted refresh if data not re-accessed

Eviction Policies

  • LRU (Least Recently Used): evict least recently accessed. Best for most workloads.
  • LFU (Least Frequently Used): evict least accessed overall. Good when some items are permanently hot.
  • TTL (Time-To-Live): expire after fixed duration. Prevents stale data.
  • FIFO: simple but poor hit rate. Rarely used in production.

Redis Data Structures for System Design

  • String: counter (INCR), session, feature flag, simple KV
  • Hash: user profile fields (HSET/HGET), object properties
  • List: activity feed, queue (LPUSH/RPOP), recent N items
  • Set: unique visitors, followers, tags (SADD/SINTER for mutual friends)
  • Sorted Set (ZSet): leaderboard (ZADD/ZRANGE), rate limiting, priority queue
  • Pub/Sub: real-time notifications, chat cross-server broadcast
  • Streams: durable event log (lightweight Kafka replacement)
  • Geo: GEOADD/GEORADIUS for nearest driver, nearby stores

Cache Problems & Solutions

ProblemCauseSolution
Cache Stampede (Thundering Herd)Many concurrent misses hit DB simultaneously after cache expiryMutex/distributed lock, probabilistic early expiry, background refresh, never-expire + async update
Cache PenetrationRepeated queries for non-existent keys bypass cache, hammer DBCache null values with short TTL; Bloom Filter to pre-check existence
Cache AvalancheMass cache expiry at same time DB overwhelmedRandomize TTL (base_ttl + random(300s)); circuit breaker; multi-layer cache
Hot Key ProblemSingle key (celebrity post) gets extreme traffic on one shardReplicate hot key to multiple shards; local in-process L1 cache; rate limit
Microservices & Architecture Patterns

Monolith vs Microservices

Monolith Pros
  • Simple to develop, test, deploy
  • No distributed system complexity
  • No network latency between components
  • ACID transactions are trivial
  • Single deployable artifact
Monolith Cons
  • Must scale entire app even if only one part needs it
  • One tech stack for everything
  • Large codebase = slow builds, hard cognitive load
  • Deploy all-or-nothing risky releases
Microservices Pros
  • Independent deploy, scale, and fault isolation
  • Technology flexibility per service
  • Small teams own individual services (Conway's Law)
  • One service crash doesn't bring down the whole system
Microservices Cons
  • Distributed systems complexity (latency, partial failures)
  • Cross-service transactions require Saga / 2PC
  • More infrastructure: service discovery, API gateway, tracing
  • Debugging is harder; need distributed tracing (Jaeger, Zipkin)
When to Choose Microservices
When different parts have drastically different scaling needs, different teams need independent release cycles, or different components need different technology stacks. NOT for early-stage premature optimization kills velocity.

API Gateway Pattern

API Gateway Pattern
Mobile App
Web App
Partner API
↓
API GatewayAuth Β· Rate Limit Β· SSL Β· Route Β· Trace Β· Transform
↓ routes to microservices
User Service:8001
Order Service:8002
Catalog Service:8003
Search Service:8004
Client (Mobile / Web / Third-party) API Gateway - Auth (JWT validation, one place) - Rate Limiting (per client / IP) - SSL Termination - Request Routing / Load Balancing - Request / Response Transformation - Logging, Distributed Tracing - Circuit Breaking User Service Order Service Catalog Service
Tools
Kong, AWS API Gateway, NGINX, Traefik, Envoy, Spring Cloud Gateway

BFF Pattern (Backend for Frontend)

  • Separate API gateway per client type (mobile BFF, web BFF)
  • Each BFF aggregates exactly the data that client needs
  • Avoids over-fetching on mobile (bandwidth matters) vs web

CQRS Command Query Responsibility Segregation

Architecture Pattern
Commands (writes) Queries (reads) Command Handler Query Handler Write Model Read Model (normalized SQL) (denormalized view, Elasticsearch, Redis) Events published Kafka Consumers update read model async Example: Orders POST /orders Write to MySQL publish OrderPlaced Kafka Kafka consumer build summary view in Redis / Elasticsearch GET /orders/summary reads from fast read store
Pros
  • Optimize reads and writes independently
  • Scale read and write services separately
  • Read model tailored per use case
  • Natural fit with Event Sourcing
Cons
  • Eventual consistency between read/write models
  • Significantly more complex architecture
  • Duplicate data, sync must be maintained
  • Overkill for simple CRUD apps

Event Sourcing

Architecture Pattern

Core Idea: Store sequence of events, not current state

  • Every state change = immutable event appended to event log
  • Current state = replay all events from the beginning
  • Like Git for your data full history, time travel, audit trail
  • Snapshots: periodically save state to avoid full replay from the beginning
Traditional: Event Sourcing: Account { balance: $150 } Event Log (append-only): 1. AccountOpened {balance: $0} 2. Deposited {amount: $200} 3. Withdrawn {amount: $50} Current state: replay all $0 + $200 - $50 = $150
Relevant to Alation
Data lineage, audit trail, metadata versioning these are core Alation use cases where event sourcing shines. Know this well.

Saga Pattern Distributed Transactions

Distributed
Problem
How do you maintain consistency across multiple microservices without a distributed ACID transaction (2PC)?

Two Saga Types

  • Choreography: Services react to domain events. No central coordinator. Decoupled but hard to visualize flow.
  • Orchestration: Central Saga Orchestrator sends commands to each service and handles failures. Easier to trace, single place for flow logic.
Choreography (Event-driven): OrderPlaced InventoryReserved PaymentProcessed Shipped (failure) PaymentFailed InventoryReleased OrderCancelled Orchestration: SagaOrchestrator reserve-inventory process-payment ship done (on any failure) compensate backwards

Outbox Pattern Reliable Event Publishing

Problem
If you write to DB and then publish to Kafka, what if Kafka publish fails? You have inconsistent state.
Solution: Transactional Outbox DB Transaction (atomic): 1. INSERT INTO orders ... 2. INSERT INTO outbox_events ... Outbox Relay (Debezium CDC or polling): Kafka downstream consumers

How It Works

  • Write business record AND outbox event in same DB transaction (atomic)
  • Separate relay process reads outbox table and publishes to Kafka
  • Relay uses CDC (Debezium reads WAL) or polling loop
  • Guarantees at-least-once delivery; consumer must be idempotent
Resilience Patterns

Circuit Breaker Pattern

Must Know
Problem
If Service B is slow, calls from A to B pile up, threads exhaust, A also fails cascading failure across the system.
Circuit Breaker State Machine
CLOSEDAll requests pass through
β†’ failures > 50% β†’
OPENFail fast β€” no calls sent
β†’ 30s timeout β†’
HALF-OPEN1–2 probe requests allowed
Normal operation
Returns cached fallback
Success→CLOSED | Fail→OPEN
CLOSED (normal) OPEN (failing) HALF-OPEN (testing) All requests flow fail fast (no requests) allow 1-2 test requests failure rate timeout expires fail OPEN succeed CLOSED exceeds threshold OPEN

Configuration

  • Failure threshold: 50% failure rate in 10s window OPEN
  • Open duration: 30s (fail fast, don't retry hammering broken service)
  • Success threshold to close: 3 consecutive successes in HALF-OPEN
  • Fallback: return cached response, default value, or degrade gracefully
  • Libraries: Resilience4j (Java), Hystrix (Netflix, deprecated), Polly (.NET)

Retry, Timeout, Bulkhead, Rate Limiting

PatternWhat It DoesKey Config
Retry + BackoffRe-attempt failed requests with exponential backoff + jittermax_retries=3, delay=2^n * 100ms + random(100ms)
TimeoutFail fast if response not received in time limitconnect_timeout=3s, read_timeout=10s
BulkheadIsolate thread/connection pools per downstream servicemaxConcurrentCalls=10 per downstream
Rate LimitingLimit incoming requests per client/IP/API key100 req/min per API key
FallbackReturn degraded response instead of errorReturn cached data, empty list, default value
Exponential Backoff + Jitter Formula
delay = min(cap, base * 2^attempt) + random(0, jitter_max). Without jitter all retriers retry simultaneously creates another thundering herd on the recovering service.

Rate Limiting Algorithms

AlgorithmHowBurstMemoryNotes
Token BucketBucket refills at rate R, each request uses 1 tokenYes (burst up to bucket size)O(1)Most common; allows burst
Leaky BucketQueue requests, process at fixed rateNo (smooth output)O(queue)Smooth traffic shaping
Fixed WindowCount per fixed minute/hour windowYes (edge burst issue)O(1)Simple; 2x burst possible at boundary
Sliding Window LogStore timestamp per request, count within windowAccurateO(requests)Accurate but memory-intensive
Sliding Window CounterBlend current + previous window proportionallyApproximateO(1)Good balance; Redis-friendly

Redis Rate Limiting (Sliding Window Counter)

  • Key: rate:{user_id}:{current_minute}
  • INCR + EXPIRE (atomic with Lua or pipeline)
  • Blend: count = current_bucket + previous_bucket * (1 - elapsed/window)
Distributed Systems Concepts

Consistent Hashing

Hash Ring (0 2^32 - 1): 0 (=2^32) S1 (@ 100) 350 200 S4 [ring] S2 300 S3 (@ 300) Key lookup: hash(key) % 2^32 walk clockwise to nearest server Adding S5 between S1-S2: only keys in [hash(S1), hash(S5)] remap Removing S2: only S2's keys move to S3 Virtual Nodes: S1 maps to 150 positions on ring (S1-vn1, S1-vn2 ...) Much more uniform distribution even with heterogeneous hardware

Used In

  • Cassandra, DynamoDB: partition data across nodes
  • Redis Cluster: slot assignment (actually uses fixed 16384 slots)
  • CDNs: route requests to nearest/least-loaded edge node
  • Load balancers: session affinity without lookup tables

Distributed Locking

ApproachHowTrade-offs
Redis SET NX EXSET lock_key owner NX EX 30 atomic set-if-not-exists with TTLFast, simple; Redlock algorithm for multi-node safety
ZookeeperCreate ephemeral sequential znodes; lowest sequence = lock holderStrongly consistent; handles crashes via ephemeral nodes
DB Pessimistic LockSELECT ... FOR UPDATE on DB rowSimple; slow; doesn't work cross-service
Optimistic Lockingversion column; UPDATE WHERE version=expected; retry on conflictNo blocking; great for low contention; retry logic needed
Redis Lock Safety Rule
Only release the lock if YOU own it: check owner == your_id before DEL. Use Lua script for atomic check-and-delete. Auto-expiry prevents deadlock if holder crashes.

Load Balancing Algorithms

AlgorithmHowBest For
Round RobinRotate through servers in orderUniform servers, stateless requests
Weighted Round RobinProportional to server capacityHeterogeneous hardware
Least ConnectionsRoute to server with fewest active connectionsLong-lived connections, variable request duration
IP Hashhash(client_ip) % N same client same serverSession stickiness (stateful apps)
Least Response TimeLowest latency + fewest connections combinedLatency-sensitive APIs

L4 vs L7 Load Balancing

  • L4 (Transport): routes by IP + TCP/UDP port. Very fast, no HTTP inspection. AWS NLB.
  • L7 (Application): routes by HTTP path, headers, cookies. Smarter. /api API servers; /static CDN. AWS ALB, NGINX.

Bloom Filters

Probabilistic data structure: "definitely NOT in set" OR "probably in set"

  • Space-efficient: represents 1M items in ~1MB vs hash set in 100MB
  • False positives possible, false negatives never
  • Cannot delete (use Counting Bloom Filter for deletion)
Use CaseHow Bloom Filter Helps
URL ShortenerCheck if short code exists before DB query
Cache PenetrationBlock queries for non-existent keys before they hit DB
Email deduplicationCheck if email already sent before DB lookup
Chrome Safe BrowsingQuick check if URL is malicious
Cassandra read pathCheck if key exists in SSTable before disk read
Networking & Communication

REST vs GraphQL vs gRPC

API Design
FactorRESTGraphQLgRPC
ProtocolHTTP/1.1 + JSONHTTP/1.1 + JSONHTTP/2 + Protobuf
SchemaOpenAPI (optional)Strongly typed SDLStrongly typed .proto
Over/Under-fetchCommon problemClient specifies exact fields neededN/A (binary, compact)
PerformanceGoodGood (1 round trip for complex data)Excellent (binary, HTTP/2 multiplexing)
StreamingSSE / WebSocketsSubscriptions (WebSocket)Bidirectional streaming native
Use casePublic APIs, simple CRUDMobile BFF, complex nested queriesInternal microservices, high-throughput
CachingEasy (HTTP cache headers)Complex (POST-based, no HTTP cache)Custom only
ToolingExcellent (Postman, curl)Good (GraphiQL, Apollo)Good (grpcurl, Buf)

WebSockets vs Long Polling vs SSE

ApproachHowLatencyDirectionUse Case
Short PollingClient requests every N secondsUp to N secondsPull onlySimple updates where delay is OK
Long PollingServer holds request open until data availableLowPull onlySimple push before WebSockets era
SSEServer pushes over persistent HTTP (text/event-stream)Very lowServer Client onlyDashboards, live feeds, notifications
WebSocketsFull-duplex TCP after HTTP upgrade handshakeVery lowBidirectionalChat, gaming, collaborative editors
WebSocket at Scale Challenge
WebSocket servers are stateful (connection on specific server). Solution: Sticky sessions at LB + Redis Pub/Sub to broadcast messages across servers. Any server can publish to Redis; subscriber servers push to their connected users.

CDN & DNS

CDN Flow: User (India) DNS CDN Edge Node (Singapore nearest) Cache Hit return immediately Cache Miss fetch from Origin (US) cache return DNS Resolution: Browser OS Cache DNS Resolver (ISP/8.8.8.8) Root NS (".") TLD NS (".com") Authoritative NS Returns IP Browser connects (TTL cached)

CDN Cache Invalidation

  • Versioned URLs (best practice): /app.v3.2.1.js new URL = new cache entry
  • TTL-based: Cache-Control: max-age=86400
  • Purge API: Explicitly invalidate specific paths on deploy (Cloudflare, Fastly)

Reverse Proxy vs Forward Proxy

  • Forward Proxy: Client-controlled. Client Proxy Internet. VPN, content filtering.
  • Reverse Proxy: Server-controlled. Internet Proxy Servers. Load balancing, SSL termination, hiding server topology.
Authentication & Security

JWT vs Session Tokens

JWT (Stateless): Header.Payload.Signature Header: {"alg":"HS256","typ":"JWT"} Payload: {"sub":"user123","roles":["admin"],"exp":1234567890} Signature: HMAC-SHA256(base64(header)+"."+base64(payload), secret) Server validates signature ONLY no DB lookup Cannot revoke before expiry (use short TTL: 15min) Refresh token (long-lived, DB-backed) to get new access token Session (Stateful): Client sends: Cookie: session_id=abc123 Server: SELECT * FROM sessions WHERE id='abc123' (Redis for speed) Can revoke instantly (delete from sessions table)
JWTSession Token
ScalabilityExcellent no shared stateNeeds shared Redis/DB across all servers
RevocationHard need token blacklist in RedisEasy delete session record
Token SizeLarge (~500 bytes)Small (random 32-byte string)
Best ForMicroservices, stateless APIs, mobileTraditional web apps, high-security (banking)

OAuth 2.0 Authorization Code Flow

1. User clicks "Login with Google" 2. App redirects: GET /oauth/authorize ?client_id=APP_ID &redirect_uri=https://app.com/callback &scope=email+profile &response_type=code &state=random_csrf_token <-- CSRF protection 3. User authenticates & grants permission at Google 4. Google redirects: GET https://app.com/callback ?code=AUTH_CODE &state=random_csrf_token <-- verify state matches 5. App backend calls: POST /oauth/token {code: AUTH_CODE, client_secret: SECRET, grant_type: authorization_code} 6. Google returns: {access_token, refresh_token, expires_in} 7. App calls: GET /userinfo with Bearer access_token 8. On expiry: POST /oauth/token {grant_type: refresh_token, refresh_token: RT}
Key Security Points
state param = CSRF protection PKCE replaces client_secret for mobile/SPA access_token = short-lived (15min) refresh_token = long-lived (30 days, stored securely server-side) Never expose client_secret in frontend
System Design Interview Questions
Interview Framework
  1. Clarify Requirements β€” functional + non-functional, scale
  2. Estimate Scale β€” RPS, storage, bandwidth
  3. High-Level Design β€” draw major components
  4. API Design β€” endpoints, data models
  5. Deep Dive β€” critical components & bottlenecks
  6. Trade-offs β€” what you chose and why
Q1

Design a URL Shortener (TinyURL)

Requirements: Shorten URLs, redirect, analytics, expiry. Write: 100M URLs/day. Read: 1B redirects/day (10x writes).
Estimates: 1B reads/day = 12K RPS. 100M writes/day = 1.2K RPS. Storage: 100 bytes * 100M * 365 days * 5 years = ~18TB.
Short Code: Base62 (a-z, A-Z, 0-9). 7 chars = 62^7 = 3.5 trillion unique codes. Use DB auto-increment ID convert to Base62.
URL Shortener Architecture
WRITE PATH β€” 1.2K RPS
Client
POST /shorten β†’
Load Balancer
β†’
Shortener Service
β†’
MySQLid β†’ short_code
READ / REDIRECT PATH β€” 12K RPS
Client
GET /abc123 β†’
Redirect Service
β†’
Redis CacheHIT: 302 redirect
MISS ↓
MySQLlookup + cache fill
ANALYTICS β€” ASYNC
Redirect Service
β†’
Kafkaclick events
β†’
Analytics Consumer
β†’
ClickHouseaggregates
Write: POST /shorten {long_url, expiry} ShortenerService INSERT into MySQL (id, short_code, long_url, expiry) Return https://short.ly/abc1234 Read: GET /abc1234 Redis cache lookup (short_code long_url) HIT: 302 Redirect long_url MISS: MySQL lookup populate Redis (TTL 24h) 302 Redirect Analytics (async): On each redirect Kafka event {short_code, timestamp, ip, user_agent} Analytics Consumer ClickHouse / DynamoDB for aggregates
Design Decisions
  • Auto-increment + Base62: no collision, simple
  • Redis cache: 99%+ redirects served from cache
  • 302 redirect (not 301): server sees every request accurate analytics
  • Async analytics: redirect latency unaffected
Trade-offs
  • Auto-increment is guessable (sequential codes)
  • Hash-based codes: possible collision need retry logic
  • Pre-generated codes pool: batch-generate offline
  • Bloom filter: check code non-existence before DB query
Q2

Design a Chat System (WhatsApp / Slack)

Requirements: 1:1 and group chat, online presence, message history, delivery/read receipts, media sharing.
Real-time Transport: WebSocket (full-duplex, bidirectional). Long Polling as fallback for restrictive networks.
Message Storage: Cassandra. RowKey = channel_id, clustering by timestamp. Write-heavy, append-only, time-ordered = perfect fit.
Chat System β€” Real-time Message Flow
AliceWebSocket
β†’ msg β†’
Chat Server AAlice's connection
PUBLISH β†’
Redis Pub/Subchannel: user:bob
β†’ DELIVER
Chat Server BBob's connection
β†’ push β†’
BobWebSocket
↓ async persist
Kafkamessages topic
β†’
Cassandramsg history by channel
Presence Service
heartbeat β†’
Redisuser:online:{id} TTL 60s
Message Flow (Alice Bob): 1. Alice sends msg via WebSocket Chat Server A 2. Chat Server A: a) Persist to Cassandra async b) PUBLISH to Redis channel "user:bob:msgs" 3. Chat Server B (Bob's connection) subscribes receives from Redis 4. Chat Server B delivers to Bob via WebSocket 5. Bob's client sends "delivered" ack Chat Server B Cassandra 6. Alice receives delivery receipt via WebSocket Group Chat (fan-out): message Kafka Fan-out consumer deliver to each member's inbox For large groups (10K+): pull model members fetch on open Presence Service: WebSocket connect SET user:online:{userId} 1 EX 60 (heartbeat every 30s) Heartbeat miss key expires user offline
Key Decisions
  • Redis Pub/Sub connects stateful WebSocket servers
  • Cassandra: ideal for high-write time-series message history
  • Offline inbox: store in DB, deliver on reconnect
  • Media: upload to S3, share URL in message
Challenges
  • Large groups (100K): fan-out on write too expensive use pull
  • Message ordering: Cassandra stores by timestamp; tie-break with UUID
  • E2E encryption: client encrypts, server stores ciphertext only
  • Message dedup: idempotency key per message to prevent duplicates on retry
Q3

Design Twitter / News Feed

Core Challenge: Fan-out on Write vs Fan-out on Read

  • Push (Fan-out on Write): When user tweets, push to all followers' Redis feeds. Fast reads. Bad for celebrities (50M followers).
  • Pull (Fan-out on Read): Merge tweets from followed users at read time. Slow reads. DB expensive.
  • Hybrid (Twitter's approach): Push for regular users, Pull for celebrities at read time. Best of both.
Twitter β€” Hybrid Fan-out Architecture
WRITE: User posts tweet
User (1K followers)
POST /tweet β†’
Tweet Service
β†’
Cassandratweets by userId
Kafkatweet-created
β†’ for <10K followers β†’
Fan-out Worker
β†’
Redis ZSetfeed:{followerId}
READ: User loads home feed
Feed Service
β†’
Redis ZSetregular tweets (pre-built)
+
Celebrity pullfetch top 20 directly
β†’ merge β†’
Feed
Tweet Write: POST /tweet TweetService Cassandra (tweets by userId + timestamp) Kafka: "tweet-created" event FanoutWorker (async): for each follower (if follower_count < 10K): ZADD feed:{followerId} tweet.timestamp tweet.id (celebrities skip pulled at read time) Feed Read: GET /feed/{userId} Feed Service: 1. ZRANGE feed:{userId} 0 20 get regular user tweet IDs 2. For each celebrity followed: fetch their latest 20 tweets directly 3. Merge all sort by timestamp return top 20 Like/RT: Redis: INCR likes:{tweetId} Async flush to Cassandra every 5min
Architecture Decisions
  • Cassandra for tweets: write-heavy, time-series pattern
  • Redis Sorted Set for feed: O(log N) insert, O(1) range read
  • Async fan-out via Kafka: tweet API returns instantly
  • S3 + CDN for media content
Trade-offs
  • Push model: more storage (each tweet copied to N followers' feeds)
  • Pull model: slow read (query N followed users, merge, sort)
  • Celebrity tweets: fan-out to 100M followers takes minutes
  • Trending: sliding window count via Redis sorted sets
Q4

Design YouTube / Video Streaming

Upload: Chunked upload to S3 Kafka event Transcoder cluster (FFmpeg) multiple resolutions (360p/720p/1080p/4K) output to CDN
Streaming: HLS (HTTP Live Streaming). Video split into ~10s segments (.ts files). Manifest file (.m3u8) lists URLs. Client adapts resolution based on bandwidth (ABR).
Metadata: MySQL for structured data (title, description, tags, owner). Redis for view counts. Elasticsearch for search.
Upload Flow: Client Chunked multipart upload (resumable) S3 (raw) S3 event Kafka: "video-uploaded" Transcoder workers (GPU instances): Input: raw.mp4 FFmpeg [360p, 720p, 1080p, 4K] + thumbnails Upload outputs CDN origin CDN distributes globally Metadata service: UPDATE videos SET status='published' WHERE id=... Playback: GET /video/{id}/manifest return .m3u8 (HLS manifest file) Client parses fetches segments from CDN edge Client detects bandwidth drop switches to lower quality segment URL
Key Decisions
  • CDN essential: video = 80%+ of internet traffic
  • HLS/DASH: industry standard, all browsers support
  • Async transcoding: instant upload response, background processing
  • Separate read/write paths (CQRS-like for metadata)
Challenges
  • Storage: 1 video 5 qualities + thumbnail + captions = 10x raw size
  • Transcoding cost: GPU instances expensive; queue management critical
  • View count: Redis INCR async flush to DB (approximate but fast)
  • Copyright: Content-ID fingerprinting pipeline runs in background
Q5

Design a Distributed Cache (like Redis Cluster)

Data Distribution: Consistent hashing. hash(key) maps key to server. Virtual nodes for uniform distribution.
Replication: 1 primary + 2 replicas per shard. Reads go to replicas (eventual consistency). Writes always to primary.
Eviction: LRU per shard. Track access time. When memory full, evict least-recently-used entries first.
Persistence: RDB (point-in-time snapshot, compact) + AOF (append-only log, durable). Use both for full safety.
Design Decisions
  • In-memory: microsecond latency vs millisecond for disk
  • Single-threaded event loop: no lock contention
  • Pipelining: batch multiple commands in one round-trip
  • Lua scripting: atomic multi-command operations
Trade-offs
  • Memory limit: working set must fit in RAM (expensive)
  • Hot key: shard replicas, local L1 cache in application
  • Replication lag: replica may briefly serve stale reads
  • Split-brain: use Redis Sentinel or Cluster for HA failover
Q6

Design Search Autocomplete (Typeahead)

Requirements: Top-5 suggestions per prefix, under 100ms latency. 10M users, 10 queries each = 100M queries/day = ~1,200 RPS.
Data Structure: Trie with frequency at each node. Or Elasticsearch prefix query with score boosting.
Ranking: Query frequency + recency + personalization weight.
Query (read, latency-critical): GET /autocomplete?q=data+cat CDN (cache popular prefixes like "data", "date", "da") Redis: ZREVRANGE prefix:data_cat 0 4 [top5 sorted by score] Trie Service (in-memory, sharded by first char) Elasticsearch fallback Update (async): User search logs Kafka Aggregation Service (5-min windows) Update term frequency Rebuild top-K per prefix Push to Redis ZSet (ZADD prefix:data_cat freq "data catalog")
Optimizations
  • Trie sharded by first 2 chars (26^2 = 676 shards)
  • Redis ZSet caches top-5 per prefix (1-hour TTL)
  • CDN caches most common prefixes globally
  • Client debounce: only query after 300ms idle
Challenges
  • Trie memory: 5M terms avg 8 chars = large in-memory footprint
  • Real-time updates to trie are expensive batch updates every 5 min
  • Personalization: blend global score + user history with weights
  • Multi-language: Unicode = more complex trie / different sharding
Q7

Design a Notification System

Event Sources: User Action / Business Alert / Marketing Campaign Notification Service API (validates payload, applies user preferences, deduplication check) async Kafka Topics (separate topics per channel): -- notifications-email Email Worker SendGrid / SES (retry + DLQ) -- notifications-sms SMS Worker Twilio (retry + DLQ) -- notifications-push Push Worker FCM / APNs (retry + DLQ) -- notifications-inapp InApp Worker WebSocket / DB Notification DB: -- Delivery status (sent, delivered, failed, read) -- User preferences (email opt-in, push enabled, quiet hours 10pm-8am)
Key Decisions
  • Idempotency key: prevents duplicate sends across retries
  • User preferences service: opt-outs, quiet hours, frequency caps
  • Priority lanes: critical alerts bypass normal queue
  • Rate limit: max 3 emails/hour per user to prevent spam
Challenges
  • Deduplication: same event published twice idempotency key check before send
  • Marketing blast to 10M: partition workers, throttle to ~100K/min
  • Delivery tracking: webhooks from SendGrid/Twilio update status
  • DLQ monitoring: alert on-call when DLQ grows beyond threshold
Q8

Design Uber / Ride-Hailing

Location Tracking: Drivers send GPS every 5s. GEOADD in Redis (O(log N) insert). GEORADIUS for nearest-driver query.
Geohashing: Encode lat/lng as a string. Nearby locations share prefix. Efficient area queries without lat/lng math.
Matching: Find N nearest available drivers send offer simultaneously first accept wins create Trip (MySQL, ACID).
Driver Location: Driver app WebSocket Location Service GEOADD drivers {lng, lat, driver_id} Ride Request: User RideRequest Service: 1. GEORADIUS(user_lat, user_lng, radius=5km) [driverA, driverB, driverC] 2. Filter: available + rating > 4.5 + correct vehicle type 3. Offer sent to top 3 simultaneously (first accept wins) 4. Accepted: INSERT INTO trips (atomic, MySQL) lock driver state Real-time Trip Tracking: Driver GPS every 3s Kafka TripTrackingService WebSocket User
Key Decisions
  • Redis Geo: O(log N) radius search, in-memory speed
  • MySQL for trips: ACID needed for payment integrity
  • Cassandra for location history: write-heavy time-series
  • Surge pricing: pre-computed by geohash zone demand
Challenges
  • Simultaneous accept: optimistic lock on driver status (available in-trip)
  • GPS accuracy: Kalman filter to smooth noisy GPS readings
  • Driver state machine: available offered in-ride available
  • ETA: integrate OSRM / Google Maps API for routing and ETA
Q9

Design a Data Catalog (like Alation)

Core Features: Metadata ingestion from databases/BI tools, search, lineage tracking, data quality scoring, collaboration (stewardship, annotations).
Metadata Storage: Graph DB (Neo4j) for lineage (A B C relationships). Elasticsearch for full-text search. PostgreSQL for structured catalog data.
Connectors: Pull metadata via JDBC/REST from source systems (Snowflake, BigQuery, Tableau). Schedule crawls. Detect schema drift.
Metadata Ingestion: Connector Framework Source System (Snowflake/BigQuery/Tableau) Extract: tables, columns, types, stats, query history Transform: normalize to internal metadata model Load: PostgreSQL (structured catalog: tables, columns, owners) Elasticsearch (search index: name, description, tags) Neo4j (lineage graph: table derived from upstream table) Event Store (schema change history for audit) Search: User query Elasticsearch (full-text + faceted filtering) Boost: verified assets, frequently accessed, recently updated Lineage: MATCH (a)-[:DERIVED_FROM*]->(b) WHERE a.name='revenue_report' Show upstream impact analysis for data quality issues
Key Decisions
  • Graph DB: lineage relationships are inherently graph-structured
  • Elasticsearch: fuzzy search, facets, ranking by relevance
  • Event Sourcing: full audit trail of metadata changes
  • CQRS: write-optimized ingest path, read-optimized search path
Challenges
  • Schema drift: source changes must detect and re-index affected assets
  • Scale: 100K+ tables, millions of columns = large search index
  • Trust/quality scoring: aggregate from multiple signals (freshness, usage, docs)
  • Real-time lineage: parse SQL query logs to extract column-level lineage
Looking for HLD Questions?
Big-infrastructure designs (Google Docs, Maps, Netflix, Drive, Payments, Instagram and more) are in the High-Level Design (HLD) β†’ page.
HLD-1

Design Google Docs (Collaborative Document Editor)

Requirements: Multiple users edit same document simultaneously in real-time. Changes visible to all within <100ms. Support text, formatting, comments, version history, offline editing, cursor presence.
Scale: 1B documents, 100M DAU, up to 100 concurrent editors per doc. 99.99% availability.
Core Challenge: Conflict resolution when concurrent edits happen. How do two users editing position 5 simultaneously converge to the same document?
Architecture: Client (Browser / Mobile) Local Document State (shadow copy + pending ops queue) WebSocket connection to Doc Server (sticky session via consistent hash on docId) Doc Server (one per active document session): Receives op from User A: {type:insert, pos:5, char:'X', rev:42, clientId:'A'} Transforms op against any concurrent ops received (OT algorithm) Appends to document Op Log (ordered) Broadcasts transformed op to all connected clients Persists to Op Storage Storage: Op Log: Cassandra (append-only, docId + revision as PK, fast writes) Document Snapshots: S3 (full doc state every 1000 ops for fast load) Metadata: MySQL (doc title, owner, ACL, created_at, last_modified) Redis: active session routing (docId β†’ server instance mapping) Real-time Sync (OT): Server serializes all ops (single authority) Client sends: op + base_revision Server transforms op against all ops since base_revision Committed op broadcast with new revision number to all clients Client algorithm: Applied locally immediately (optimistic): instant feedback Receive server ack: adjust revision counter Receive other user's op: transform against local pending ops

Cursor Presence & Awareness

  • Each user's cursor position = op with {type: cursor, pos, userId, color}
  • Broadcast via same WebSocket channel but not persisted (ephemeral)
  • Throttled: send cursor updates max every 50ms (debounce)
  • Display: other cursors rendered as colored carets with username label

Offline Editing & Sync

  • Client stores pending ops in IndexedDB while offline
  • On reconnect: send all buffered ops with last-known revision
  • Server replays and transforms against ops committed while offline
  • CRDTs (alternative): Automerge/Yjs β€” no server-side transformation needed, peer-to-peer merge
Key Design Decisions
  • OT with central server: battle-tested, Google's actual approach
  • Cassandra for op log: append-only + high write throughput
  • Snapshot every N ops: fast doc load without replaying all ops
  • WebSocket per doc server: low latency, stateful connection
  • Consistent hashing on docId: all clients of same doc β†’ same server
Deep Dive Questions
  • How to handle server crash during active session? (Redis TTL + reconnect)
  • How to load a 500-page doc quickly? (latest snapshot + delta ops)
  • Permission check on every op? (token in WebSocket handshake, server enforces)
  • Comments vs inline edits: separate op types, thread anchored to character range
HLD-2

Design Google Maps (Navigation & Geo Service)

Requirements: Search POIs, get directions (car/walk/transit), real-time traffic overlay, ETA, turn-by-turn navigation, map tile rendering, live traffic contribution from users.
Scale: 1B+ users, 25M+ updates/day from users. Map data = petabytes. 99.99% uptime for navigation.
System Components: 1. Map Tile Service: World map pre-rendered into tiles at 21 zoom levels Tile naming: zoom/x/y.png (quadtree coordinates) Stored on Object Storage (GCS) served via CDN globally Vector tiles (modern): send vector data, render client-side (better scaling, dark mode) Tile generation pipeline: OSM data + satellite imagery Mapnik/custom renderer β†’ tiles 2. Routing Engine: Road network = weighted directed graph Nodes: intersections Edges: road segments with weights (distance, speed_limit, traffic) Algorithm: Bidirectional Dijkstra (15x faster than one-directional) For Google-scale: Contraction Hierarchies (CH) precompute shortcuts Preprocess offline: add shortcut edges bypassing less important nodes Query: expand upward from src AND downward from dst meeting in middle Result: routes in <1ms even for continent-scale 3. Real-time Traffic: Driver location probes β†’ Kafka (every 3s while driving, anonymized) Traffic aggregation service: compute avg speed per road segment (15s windows) Traffic overlay: Flink stream processor updates segment weights in Redis Routing engine reads current edge weights from Redis for ETA 4. ETA Prediction: Historical + real-time traffic + ML model (time of day, day of week, weather) Continually recalculate during navigation if user deviates or traffic changes 5. Search (POI): "Coffee near me" β†’ Elasticsearch geo_distance query + ranking by ratings/distance Named search "Eiffel Tower" β†’ Knowledge Graph (entity resolution) β†’ coordinates Geocoding (address β†’ lat/lng): Nominatim / in-house model Reverse geocoding (lat/lng β†’ address): R-tree spatial index
Key Decisions
  • Contraction Hierarchies: routing on 10M+ node graph in <1ms
  • Quadtree tiles + CDN: 99%+ tile requests served from edge cache
  • Kafka for traffic probes: ingests 100M+ updates/day without loss
  • Vector tiles: client rendering, reduced data transfer, theme support
  • Segment-level traffic: granular road speed, not just major highways
Deep Dive Questions
  • Alternative routes: k-shortest paths, re-weight edges with diversity penalty
  • Offline maps: pre-download region tiles + routing graph, sync on WiFi
  • Tunnel/GPS loss: dead reckoning (speed + heading extrapolation) + map snapping
  • Map updates: OSM community + satellite ML detection β†’ daily tile re-renders
HLD-3

Design Google Drive / Dropbox (File Sync & Storage)

Requirements: Upload/download files, sync across devices, versioning, sharing (link + ACL), deduplication, conflict resolution when same file edited on two devices offline.
Scale: 500M users, 50M DAU, avg 2GB per user = 1 exabyte total. 10M uploads/day.
Upload Flow: 1. Client chunks file into 4MB blocks (content-addressed: SHA256 of block) 2. Client checks: which blocks already exist? POST /api/check_blocks [sha256_list] 3. Server: SELECT sha256 FROM blocks WHERE sha256 IN (...) return existing 4. Client uploads ONLY missing blocks (delta sync β€” huge bandwidth savings) PUT /upload/block/{sha256} direct to S3 via pre-signed URL (bypass app servers) 5. Client: POST /api/commit_file {filename, block_list: [sha256s], parent_version} 6. Server: INSERT file record + version in MySQL, notify other devices via SSE/WebSocket Block Storage (Deduplication): blocks table: {sha256 (PK), s3_path, size, ref_count} files table: {file_id, user_id, version_id, block_list (JSON array of sha256s)} Two users upload same file? Both reference same blocks (storage saved!) Garbage collection: decrement ref_count on delete; S3 deletion when ref_count=0 File Metadata: MySQL: file hierarchy (parent_folder_id, name, user_id, permissions) Redis: active user sessions, recently accessed file metadata (hot cache) Elasticsearch: full-text filename search across all files Sync Protocol: Client maintains local delta log (what changed since last sync) Server sends: GET /changes?since=cursor (long-poll or WebSocket) Cursor: opaque token representing sync state (like Dropbox's /list_folder/continue) On change: server publishes event Kafka notifications-{userId} SSE/WebSocket push Conflict Resolution: Two devices edit file while offline, reconnect with divergent versions Strategy: keep BOTH files: "report.docx" + "report.docx (conflict copy, Device A, 2024-01-15)" User resolves manually (same as Dropbox behavior β€” don't attempt auto-merge for binary files)
Key Decisions
  • Content-addressed blocks: dedup + only upload changed chunks (delta)
  • Pre-signed S3 URLs: offload large uploads directly to S3, not app servers
  • MySQL for metadata: ACID needed for file hierarchy + permissions
  • Chunked upload: resume interrupted uploads (only re-upload missing chunks)
Deep Dive Questions
  • Large file upload (10GB): multipart S3 upload, 5min pre-signed URL expiry
  • Version history: store block_list per version, rollback = re-commit old block_list
  • Shared folder sync: all members receive change notifications
  • Client-side encryption: encrypt before upload; server stores ciphertext only
HLD-4

Design Netflix / Video Streaming Platform

Requirements: Stream video on-demand (200M+ subscribers), personalized recommendations, search, content upload pipeline for studios, global CDN delivery, adaptive bitrate.
Scale: 200M subscribers, 100M+ concurrent streams at peak, 15% of global internet traffic.
Content Upload Pipeline (Studio β†’ Users): Studio uploads raw 4K master β†’ S3 (raw-content bucket) S3 event β†’ Kafka "content-uploaded" topic Transcoding Fleet (GPU instances β€” EC2 P4d): Input: raw.mp4 FFmpeg transcodes to: 240p/360p/480p/720p/1080p/4K Codec: H.264 (compatibility) + HEVC/H.265 (60% smaller, for 4K) Output: HLS/DASH segments (.ts files, ~6s each) + .m3u8 manifests Upload segments β†’ CDN Origin (S3 bucket closest to edge) Metadata: MySQL (title, cast, genre, synopsis, age_rating) Elasticsearch: title + genre + cast search Playback (Client β†’ CDN): Client: GET /api/manifest/{contentId} β†’ returns .m3u8 with CDN URLs Client requests segments: https://cdn-edge.netflix.com/show123/1080p/seg001.ts CDN edge (Open Connect Appliance β€” Netflix's own CDN hardware in ISP PoPs) Edge cache: 99%+ of popular content served directly from ISP network! Adaptive Bitrate (ABR): client monitors download speed If bandwidth drops: switch from 1080p manifest to 720p, seamless mid-stream Recommendation Engine: Offline: Spark batch jobs on viewing history β†’ collaborative filtering (ALS matrix factorization) Near-real-time: Kafka stream β†’ Flink updates user affinity scores Serving: pre-computed top-N recommendations per user stored in Cassandra Personalized thumbnails: A/B test different thumbnail images per user profile Resilience (Netflix Chaos Engineering): Circuit breakers between all microservices (Hystrix/Resilience4j) Chaos Monkey: randomly kills production instances (tests resilience) Multi-region active-active: US-East, US-West, EU, Asia Fallback: if recommendations unavailable β†’ return editorial "top picks"
Key Decisions
  • Open Connect (own CDN in ISPs): eliminates 95%+ of transit costs
  • HLS/DASH + ABR: smooth experience across all network conditions
  • Cassandra for viewing history: write-heavy time-series, global multi-DC
  • Chaos engineering: force resilience by breaking prod intentionally
Deep Dive Questions
  • Cold start for new content: pre-load segments to edges before release
  • DRM: Widevine (Google) / PlayReady (Microsoft) segment encryption
  • Bandwidth estimation for CDN: 200M users Γ— avg 5 Mbps = 1 Pbps capacity
  • Search: Elasticsearch with ML-reranked results based on user profile
HLD-5

Design a Web Crawler (like Google Search Indexer)

Requirements: Crawl 1B+ web pages, extract links, store content, respect robots.txt, avoid duplicate content, refresh stale pages, handle dynamic JS pages.
Scale: 1B pages, avg 100KB/page = 100TB content. Re-crawl top pages every 24h, others weekly. 10K pages/sec sustained crawl rate.
Architecture: URL Frontier (Priority Queue): Stores URLs to crawl, prioritized by: - PageRank estimate (important pages crawled more often) - Freshness score (last modified headers, update frequency) - Domain politeness (max 1 req/sec per domain) Implementation: Kafka topics per domain + Redis sorted set for priority Crawl Workers (stateless, horizontally scalable): Dequeue URL from Frontier DNS resolve (local cache + upstream) Fetch page (HTTP GET with User-Agent: Googlebot, respect robots.txt) Dynamic pages: headless Chrome (Puppeteer) for JS rendering Extract: all href links + raw HTML content Publish to Kafka: "pages-crawled" topic: {url, html, timestamp, status_code} "links-found" topic: {source_url, [discovered_urls]} Content Processing Pipeline: HTML Parser β†’ extract text, metadata, canonical URL Dedup Service: SHA256(normalized_content) β†’ Bloom Filter β†’ URL Set check Duplicate? Skip. New? Store in object storage (S3), index in Elasticsearch URL Deduplication: Bloom filter (1B URLs, 1% FP rate, ~1.2GB RAM) β€” fast pre-check URL Set (Redis / DynamoDB) β€” authoritative dedup store URL normalization: lowercase, remove fragment, canonical redirect resolution robots.txt Compliance: Fetch and cache robots.txt per domain (TTL 24h) Before crawling any URL: check allow/disallow rules Crawl-delay directive respected Refresh Strategy: High-priority (news, social): re-crawl every 1h via separate high-frequency queue Normal pages: adaptive based on historical change rate (3 days if rarely changes) 404 pages: exponential backoff (1d, 4d, 16d), eventually discard URL
Key Decisions
  • Kafka per-domain topics: natural domain-rate-limiting
  • Bloom filter: O(1) dedup check for 1B URLs in 1GB RAM
  • Headless Chrome pool: handle JS-heavy SPAs (React, Vue apps)
  • Adaptive refresh: crawl budget allocated by page importance
Deep Dive Questions
  • Spider traps: infinite URLs (date params, session IDs) β†’ URL length limit + depth limit
  • DNS cache poisoning: validate IPs against known-malicious list
  • Politeness: per-domain rate limit, respect crawl-delay, identify as bot
  • Distributed URL frontier: shard by domain hash, ensure politeness per shard
HLD-6

Design a Payment System (like Stripe / PayPal)

Requirements: Accept card payments, process transactions, handle refunds, prevent double-charges, financial reconciliation, fraud detection. ACID transactions are non-negotiable.
Non-Functional: Exactly-once payment processing (never charge twice). 99.999% availability. PCI-DSS compliance. Sub-3s payment confirmation.
Payment Flow (Critical Path): 1. Client: POST /payments {amount, currency, payment_method_token, idempotency_key} 2. Payment Service: Check idempotency key (Redis NX) β€” if exists, return cached result 3. Fraud Detection Service (async check within 200ms): ML model: card velocity, geo anomaly, device fingerprint β†’ risk score >0.8 risk: decline. 0.5-0.8: 3DS challenge. <0.5: proceed 4. PSP (Payment Service Provider) call: POST to Stripe/Adyen API β€” Tokenized card (PAN never stored on our servers β€” PCI-DSS scope reduction) β€” PSP charges card issuer via card network (Visa/MC rails) 5. On PSP response: SUCCESS: INSERT INTO ledger (txn_id, amount, status='completed') β€” ACID FAILURE: INSERT INTO ledger (txn_id, amount, status='failed') TIMEOUT: async reconciliation job checks PSP status endpoint 6. Return payment result to client 7. Async: emit PaymentCompleted event β†’ Kafka β†’ Order Service, Email, Analytics Double-Charge Prevention: Idempotency key (client-generated UUID) stored in Redis with TTL 24h PSP-level idempotency: pass our idempotency key in PSP request header DB: UNIQUE constraint on (idempotency_key) Ledger (Double-Entry Bookkeeping): Every financial event creates TWO entries (debit + credit): DEBIT: customer wallet $-50 CREDIT: merchant wallet $+50 (or escrow pending settlement) Sum of all ledger entries must always = 0 (invariant checked hourly) Append-only: never UPDATE ledger rows β€” only INSERT corrections Reconciliation: Nightly job: compare our ledger against PSP settlement report Mismatches (our-success + PSP-fail): auto-refund and alert Mismatches (our-fail + PSP-success): charge PSP for reverse or log for manual review
Key Decisions
  • Double-entry ledger: financial correctness, easy auditing
  • Idempotency at every layer: client β†’ service β†’ PSP
  • Tokenization: PAN never stored (PCI-DSS compliance)
  • Async fraud detection: 200ms budget, non-blocking on payment path
  • MySQL/PostgreSQL: ACID for all financial writes
Deep Dive Questions
  • PSP timeout: store as "pending", cron job polls PSP status, update ledger async
  • Currency conversion: lock exchange rate at payment time, store in transaction row
  • Chargebacks: reverse ledger entries, hold merchant funds in escrow during dispute
  • Rate limiting: max 5 payment attempts/hour per card to prevent card testing attacks
HLD-7

Design Instagram / Photo Sharing

Requirements: Upload photos/videos, follow users, home feed, explore/search, stories (24h TTL), likes, comments, DMs, notifications. 1B+ users, 100M photos/day.
Photo Upload: Client β†’ Upload Service (pre-signed S3 URL) β†’ S3 (original) S3 event β†’ Image Processing Worker (Kafka): Resize to: thumbnail (150px), feed (640px), HD (1080px), original Convert to WebP (30% smaller than JPEG) Generate perceptual hash (dHash) for duplicate detection Store all variants in S3, URLs in MySQL Feed Architecture (Hybrid Fan-out like Twitter): Follower count < 10K (regular users): Fan-out on write Post created β†’ FanoutWorker β†’ ZADD feed:{followerId} timestamp postId Feed stored in Redis Sorted Set per user Celebrity (>10K followers): Fan-out on read At read time: fetch latest N posts from celebrity's timeline directly Merge with user's pre-built feed (client or feed service merges) Feed Read: Redis ZRANGE feed:{userId} 0 50 β†’ postIds Batch fetch post metadata from MySQL/Redis Batch fetch user metadata (avatar, username) from User Service cache Return assembled feed Stories: Upload same as photos, but metadata: TTL = created_at + 86400s Redis Sorted Set: stories:{userId} (score = expiry_time) Background job: ZREMRANGEBYSCORE remove expired stories Viewer set stored in Redis Set (story:{storyId}:viewers) with same TTL Explore Feed: Content-based: user's interest graph β†’ candidate posts β†’ ML ranking Collaborative filtering: users similar to you liked β†’ show their liked posts Trending hashtags: sliding window count in Redis Likes Counter: Redis INCR likes:{postId} (in-memory, fast) Async flush to PostgreSQL every 60s (batch update) On read: check Redis first, fallback to DB
Key Decisions
  • Hybrid fan-out: pre-built Redis feed for regular users, lazy pull for celebrities
  • WebP + multiple resolutions: 30% bandwidth reduction, fast load
  • CDN for all media: images served from edge (global low latency)
  • Cassandra for DMs: write-heavy, time-ordered messages per conversation
Deep Dive Questions
  • Perceptual hash dedup: same photo re-uploaded β†’ point to same S3 object
  • Shadowban: filter posts in feed assembly without notifying user
  • Feed with follow/unfollow: update fan-out set membership atomically
  • Reels (short video): same as YouTube but max 90s, same HLS pipeline
HLD-8

Design a Distributed Task Scheduler (like Airflow / Temporal)

Requirements: Schedule tasks (one-time + cron), assign to workers, retry on failure, handle worker crashes, task dependencies (DAGs), exactly-once execution, monitor task status, priority queues.
Scale: 100M tasks/day, 10K concurrent workers, tasks must run within 1s of scheduled time.
Architecture: Scheduler (Leader-elected via Zookeeper/etcd): Cron expressions β†’ parse next N execution times (pre-compute 5min ahead) INSERT INTO task_queue (task_id, run_at, status='pending', priority) Leader heartbeat: if leader dies, Raft election for new scheduler leader Task Queue: Tier 1: Redis Sorted Set (score = run_at timestamp) High-frequency polling by dispatcher (every 100ms) ZRANGEBYSCORE task_queue -inf NOW LIMIT 0 100 β†’ due tasks ZREM + atomically claim via Lua script (prevent double-dispatch) Tier 2: MySQL (durable, for recovery after Redis flush) Redis populated from MySQL on startup; MySQL is source of truth Dispatcher: Polls Redis every 100ms for due tasks Assigns to available worker via consistent hashing (task_type β†’ worker pool) Worker heartbeats via Redis key (worker:{id} EX 30s) On heartbeat miss: task reassigned (at-least-once delivery) Workers (stateless pool): Long-poll dispatcher queue or subscribe to Kafka topic per task_type Execute task logic (idempotent!) On completion: UPDATE task_executions SET status='done', result=... WHERE id=... On failure: retry with exponential backoff (max_retries configurable) Poison pills (always-failing): move to DLQ after max_retries DAG Dependencies: task_dependencies table: {task_id, depends_on_task_id} After task completes: check if any downstream tasks now have all dependencies met If yes: INSERT downstream tasks into task_queue Exactly-Once Guarantee: Lease-based: worker gets lease (30s), must renew; if missed, task released to another worker Task execution ID: unique per attempt β†’ DB UNIQUE constraint prevents double-insert of result Idempotent task logic: worker code must handle re-execution gracefully
Key Decisions
  • Redis sorted set: O(log N) scheduling, ~10ms dispatch latency
  • MySQL as durable backup: recover all pending tasks on Redis restart
  • Leader election via etcd: prevents multiple schedulers double-scheduling
  • DLQ: poisonous tasks isolated, don't block healthy tasks
Deep Dive Questions
  • Time zone handling: store all times in UTC, convert at display layer
  • At-most-once (financial jobs): 2PC between scheduler DB and task execution record
  • Worker autoscaling: monitor queue depth, scale workers up/down via K8s HPA
  • Cascading failures: rate limit task retries, circuit break task_type if too many failures
HLD-9

Design Amazon E-Commerce (Product, Cart, Orders, Inventory)

Requirements: Product catalog, search, cart, checkout, order management, inventory, pricing, recommendations. 300M active users, 1M orders/day, Black Friday 10Γ— peak.
Service Decomposition: Product Catalog Service β†’ MySQL (structured) + Elasticsearch (search) + Redis (hot products) Cart Service β†’ Redis (session-like, ephemeral, fast writes/reads) Order Service β†’ MySQL (ACID for order lifecycle) Inventory Service β†’ MySQL (ACID for stock counts, pessimistic lock for reserve) Pricing Service β†’ Rules engine + dynamic pricing + Redis cache (TTL 5min) Recommendation Service β†’ Cassandra (view/purchase history) + ML offline model Checkout Flow (Distributed Transaction β€” Saga): 1. Cart Service: validate cart items still available 2. Inventory Service: RESERVE stock (decrement available_count, optimistic lock on version) If stock conflict: return "out of stock", rollback reservation 3. Payment Service: charge card (idempotency key = orderId) If payment fails: compensate β†’ Inventory Service RELEASE reservation 4. Order Service: CREATE order record with status='confirmed' 5. Inventory Service: COMMIT reservation (move from reserved β†’ sold) 6. Kafka: OrderConfirmed β†’ Fulfillment, Email, Analytics Inventory Concurrency (Flash Sales β€” 10K req/sec for 1 item): Approach 1: Redis DECR available:{productId} (optimistic, fast) Check >= 0 before decrement via Lua script (atomic) Flush to DB periodically and on order commit Approach 2: DB Pessimistic lock SELECT stock FOR UPDATE UPDATE stock SET count -= 1 WHERE id=... Safe but serialized β†’ throughput bottleneck under high concurrency Product Search: Elasticsearch index: title, description, brand, category, attributes (faceted) Relevance: BM25 score + boost by sales rank + personalization re-rank Facets: filter by price_range, brand, rating, Prime eligibility Spell correction: fuzziness parameter in ES query Flash Sale Architecture: Pre-sale: pre-allocate tokens in Redis (1 token = 1 item reservation right) User claims token first (lottery / first-come): RPOP flash_tokens:{productId} Got token: proceed to checkout flow with guaranteed inventory Prevents overselling + reduces DB load during spike
Key Decisions
  • Saga (Choreography): each service publishes/consumes events independently
  • Redis for cart: sub-millisecond reads, no ACID needed for cart
  • Optimistic locking for inventory: high-concurrency without DB blocking
  • Flash sale tokens in Redis: decouple demand spike from DB writes
Deep Dive Questions
  • Cart persistence: sync Redis cart to DB on checkout (prevent loss on expiry)
  • Price consistency: lock price at cart-add or at checkout? (checkout is correct)
  • Returns: saga compensation chain β†’ refund payment + restore inventory
  • Multi-warehouse: nearest warehouse with stock for fastest delivery ETA
HLD-10

Design a Global CDN / Content Delivery Network

Requirements: Serve static + dynamic content globally with <50ms latency. Cache 90%+ of requests at edge. Handle 10 Tbps peak traffic. DDoS protection. 99.999% uptime. Support video streaming, file downloads, API acceleration.
Architecture Layers: 1. DNS + Anycast Routing: Customer's domain CNAME β†’ cdn.example.com Anycast: same IP announced from all PoPs User's DNS query routes to geographically nearest PoP automatically GeoDNS alternative: return different IP based on user's location 2. Edge PoP (Point of Presence): Physical servers in 200+ cities, co-located in major ISPs/IXPs Local SSD cache: hot content stored on 100TB NVMe per PoP Software: Nginx/Varnish as cache layer + Envoy for load balancing TLS termination at edge (reduce round-trip latency dramatically) On cache miss: fetch from Regional Mid-tier cache or Origin Shield 3. Tiered Cache Architecture: Edge (city-level) β†’ Regional Mid-tier (continent) β†’ Origin Shield β†’ Customer Origin Mid-tier collapses cache misses: 1000 edge misses β†’ 1 request to origin Origin Shield: single PoP that faces origin (protects origin from traffic) 4. Cache Key & Invalidation: Default cache key: URL + Vary headers (Accept-Encoding, Accept-Language) Surrogate keys (cache tags): tag content by type (product:{id}) Purge by tag: invalidate all product:{12345} URLs atomically across all PoPs CDN propagates purge via gossip protocol to all edge nodes (<1s global) 5. Dynamic Content Acceleration: TLS connection maintained from edge to origin (TCP connection reuse, no handshake per user) HTTP/2 multiplexing + TCP optimization (CUBIC congestion control at edge) Route via private CDN backbone (faster + more reliable than public internet) Argo Smart Routing (Cloudflare): real-time network monitoring picks fastest path 6. DDoS Mitigation: Volumetric (L3/L4): BGP blackhole + traffic scrubbing centers absorb floods Application (L7): rate limiting per IP, WAF rules (block SQLi, bad bots) Anycast absorbs UDP floods: traffic spread across all PoPs, no single point overwhelmed Challenge pages: JS challenge / CAPTCHA for suspicious IPs
Key Decisions
  • Anycast: automatic failover + DDoS absorption across 200+ PoPs
  • Tiered caching: collapse origin requests by 100-1000Γ—
  • Private backbone: 30-50ms faster than public internet for cross-continent
  • Surrogate keys: surgical cache invalidation without full purge
Deep Dive Questions
  • Video delivery: range request support (byte-range caching per segment)
  • Geo-blocking: edge checks IP geolocation against blocklist before serving
  • Hot object stampede: request coalescing (only 1 request to origin for same uncached URL)
  • HTTPS everywhere: HSTS preloading, OCSP stapling at edge for fast TLS
HLD-11

Design a Metrics & Monitoring System (like Prometheus + Grafana)

Requirements: Collect metrics from thousands of services, store time-series data, query for dashboards, trigger alerts, scale to 1M+ metrics, 1-second granularity, 90-day retention.
Architecture: Collection (Push vs Pull): Pull model (Prometheus): scrape /metrics endpoint of each service every 15s Pros: service doesn't need to know about monitoring infra Cons: requires service discovery, misses very short-lived processes Push model (StatsD, CloudWatch Agent): Service pushes metrics to aggregation agent Better for serverless/ephemeral functions Ingestion Pipeline (1M metrics/sec): Services β†’ Kafka (metrics-raw topic, partitioned by service_name) Stream processor (Flink): aggregate raw points β†’ 1-min rollups β†’ 5-min rollups Write to TSDB (Time-Series DB) Storage β€” Time-Series Database: Data shape: (metric_name, labels, timestamp, value) E.g.: {name: "http_requests_total", labels: {service:"api", status:"200"}, ts: 1715000000, val: 1234.5} Options: Prometheus TSDB: local disk, chunks stored as compressed delta encoding InfluxDB: purpose-built, tag-based indexing, Flux query language Thanos / Cortex (Prometheus at scale): add S3 for long-term storage, global query Compression trick: store delta-of-deltas for timestamps + XOR compression for values Gorilla encoding (Facebook): 1.37 bytes/data point (vs 16 bytes raw) Query Layer: PromQL: rate(http_requests_total{status="500"}[5m]) β†’ 5-min error rate Pre-computed rollups: 1s β†’ 1min β†’ 5min β†’ 1hr aggregates for fast dashboard queries Cache query results in Redis (30s TTL) for popular dashboard queries Alerting: Alertmanager: evaluate alert rules every 15s against recent metrics Alert: http_error_rate > 0.01 for 5m β†’ fire PagerDuty/Slack Deduplication: group related alerts, suppress flapping (pending state buffer) On-call routing: team owns service β†’ route to that team's PagerDuty schedule
Key Decisions
  • Kafka buffer: handles ingest spikes without dropping metrics
  • Pre-aggregated rollups: fast dashboard queries without scanning raw data
  • Gorilla compression: 12Γ— storage reduction for time-series
  • S3 long-term: cheap historical data, query via Thanos/Athena
Deep Dive Questions
  • High cardinality labels (userId as label): cardinality explosion, avoid in metrics (use logs instead)
  • Cross-DC aggregation: Thanos sidecar merges metrics across regions globally
  • Anomaly detection: seasonal decomposition + z-score alerting (ML-based)
  • Retention tiers: 15s granularity 7 days, 1min granularity 30 days, 1hr 1 year