System Design Pro (complete)

66System Design Pro — Complete Reference

What this module is

Pro-grade system design reference. Replaces buying a separate book. Every building block in depth, 15+ worked end-to-end designs, distributed systems internals, DB internals, networking depth, capacity math, anti-patterns, real-world case studies.

  • Interview framework + capacity math cheat sheet
  • Each building block deep: LB, cache, CDN, queue, search, vector DB, object store
  • Database internals (B-tree, LSM, MVCC, replication protocols, isolation deep)
  • Network deep (TCP, HTTP/2/3, QUIC, gRPC, WebSocket, SSE)
  • Distributed systems: Paxos, Raft, vector clocks, CRDTs, gossip
  • Consistency models (linearizable → eventual)
  • 15+ end-to-end designs: URL shortener, chat, news feed, Uber, Twitter, YouTube, Dropbox, Slack, Netflix, payment, search, ads, rate limiter, notification, recommendation
  • Anti-patterns + scaling pitfalls

66.1 Interview framework

  1. Clarify (5 min): users, scale, primary use cases, SLA, regions, mobile vs web, read/write ratio
  2. Capacity estimate (5 min): DAU → QPS, storage, bandwidth. Use orders of magnitude.
  3. API contracts (5 min): top endpoints, request/response shapes
  4. Data model (5 min): entities + relationships + primary access pattern
  5. High-level diagram (10 min): client → LB → app → DB → cache → queue → workers → CDN
  6. Deep dive (15–20 min): pick hardest component; sharding, replication, caching, queue patterns
  7. Scale + failures (10 min): 10× growth, region outage, hot key, cascading failure
  8. Trade-offs throughout: SQL vs NoSQL, push vs pull, sync vs async, strong vs eventual

66.2 Capacity math (memorise)

ConversionValue
DAU → avg QPSDAU × actions/user/day ÷ 86,400
Avg → peak QPS×5 to ×10 typically
1 KB × 1M req/s1 GB/s bandwidth
1 GB Postgres~10M small rows or 1M moderate
1 Postgres node5K-20K simple reads/sec
1 Redis node100K+ ops/sec
1 Node.js / FastAPI process10K-50K simple req/sec
Kafka per broker~1 GB/s write
S3 cost~$0.023/GB-month (Standard)
EC2 m5.large~$70/month always-on

Latency numbers everyone should know

OpTime
L1 cache1 ns
L2 cache4 ns
Main memory100 ns
Compress 1 KB with zip3 µs
Send 1 KB over 1 Gbps10 µs
SSD random read150 µs
Read 1 MB sequentially from memory250 µs
Round trip same datacenter500 µs
Read 1 MB sequentially from SSD1 ms
HDD seek10 ms
Read 1 MB sequentially from disk20 ms
Round trip CA → Netherlands150 ms

66.3 Building blocks (each deep)

Load Balancer

CDN

Cache

Message Queue

Search Engine

Vector DB

Object Storage

66.4 Database internals (deep)

B-tree (Postgres, MySQL default)

LSM tree (Cassandra, RocksDB, LevelDB)

MVCC (Multi-Version Concurrency Control)

Isolation levels (deep)

LevelPreventsAnomalies allowed
READ UNCOMMITTEDDirty reads
READ COMMITTEDDirty readsNon-repeatable + phantom + lost updates
REPEATABLE READ+ Non-repeatablePhantoms (in SQL spec; PG snapshot isolation prevents)
SNAPSHOTDirty + non-rep + phantom (uses snapshot)Write skew
SERIALIZABLEEverythingNone

Replication protocols

Sharding

66.5 Distributed systems (deep)

CAP theorem (revisited)

In a partition, choose Consistency or Availability. Partition is unavoidable in real systems → effectively choose CP or AP.

PACELC

Extends CAP. If Partition: A vs C. Else: Latency vs Consistency. Captures the always-on trade-off, not just partition behaviour.

Consistency models (full hierarchy, strongest → weakest)

  1. Linearizable (atomic): every op appears instantaneously between invocation and response.
  2. Sequential: all clients see same global order; not necessarily real-time.
  3. Causal: causally related operations seen in same order; concurrent ops can differ.
  4. Read-your-writes: user sees own writes.
  5. Monotonic reads: never see older value once you've seen newer.
  6. Monotonic writes: writes from same client applied in order.
  7. Eventual: replicas converge given no writes.

Consensus algorithms

Paxos (Lamport, 1989)
Raft (Ongaro & Ousterhout, 2014)
ZAB (ZooKeeper Atomic Broadcast)

Vector clocks

Per-node counter, exchanged with each message. Compare two vectors:

Used by Dynamo, Voldemort to detect conflicts; client / app must resolve.

CRDTs (Conflict-Free Replicated Data Types)

Gossip protocols

Two-phase commit (2PC)

Saga pattern

66.6 Networking deep

TCP

HTTP/1.1 vs HTTP/2 vs HTTP/3

VersionTransportKey feature
HTTP/1.1TCP, textKeep-alive, pipelining (rarely used)
HTTP/2TCP, binaryMultiplexed streams, HPACK header compression, server push
HTTP/3QUIC (UDP)0-RTT handshake, no HOL blocking at transport, connection migration

gRPC

WebSocket

SSE (Server-Sent Events)

TLS handshake (TLS 1.3)

DNS

66.7 Failure handling toolkit

66.8 Worked end-to-end designs (15+)

(1) URL shortener (e.g. bit.ly)

Scale: 100M URLs created/year → 3 URLs/sec. Reads dominate writes ~100:1.

(2) Distributed unique id generator (Snowflake)

64-bit ID:

Time-sortable, no central coord. Modern: UUIDv7 (similar guarantees, standard).

(3) Rate limiter

(4) Chat (1-1 + group)

Scale: 1B users; 50 msgs/user/day = 50B/day = 580K msgs/sec average.

(5) News feed (Twitter / X)

(6) Uber-like ride matching

(7) YouTube / video streaming

(8) Dropbox-like file sync

(9) Search autocomplete

(10) Distributed cache (Memcached / Redis cluster)

(11) Distributed file system (HDFS / GFS)

(12) Notification system

(13) Payment system

(14) Ads serving

(15) Recommendation engine (Netflix / YouTube)

(16) Real-time analytics / dashboards

66.9 Architectural styles

StyleWhen fitsTrade-offs
MonolithSmall team, single domain, simple opsEasier to build/operate; harder to scale teams; single deploy
Modular monolithMid-size; clear module boundariesTransition path; still one deploy
MicroservicesMany teams; bounded contexts; need independent scaleComplex ops; network failures; data consistency
ServerlessEvent-driven, sporadic load, pay-per-useCold starts; vendor lock-in; debugging
Event-drivenDecoupled producers/consumers; async workflowsEventual consistency; debugging traces
CQRS + Event SourcingAudit trail required; complex projectionsReplay complexity; harder local dev
Cell-basedMassive scale; blast-radius isolationCross-cell routing; rebalancing

66.10 Anti-patterns + scaling pitfalls

66.11 Real-world case-study takeaways

Netflix

Twitter / X

WhatsApp

Discord

Stripe

Amazon DynamoDB

Module 68 — System Design Pro Q&A (40+)

Walk through the framework you use for any system design.
Clarify → estimate → API → data model → diagram → deep dive → scale + failures → trade-offs. Talk constantly. Pick depth over breadth in the deep dive.
How do you do back-of-envelope capacity math?
DAU × actions/user/day ÷ 86,400 = avg QPS. Peak ≈ avg × 5-10. Storage: bytes/record × records/day × retention. Bandwidth: req/sec × bytes/req. Always show your work.
L4 vs L7 load balancer?
L4 routes by TCP info (IP/port); fast, protocol-agnostic. L7 routes by HTTP (path, header, cookie); supports SSL termination, A/B routing, rewriting. L7 for HTTP services; L4 for DBs / TCP services.
Cache-aside vs write-through vs write-back?
Cache-aside: app reads cache first, on miss reads DB + populates. Write-through: writes go to cache and DB simultaneously. Write-back: writes to cache; flushed to DB later (risky on cache loss).
What's a hot key in caching and how to handle?
A single key receiving disproportionate traffic. Mitigations: replicate the key across cache nodes; tier with a local in-process cache in front of distributed cache; randomize key suffix (key_1..key_10) and aggregate.
How does consistent hashing work?
Map keys + nodes onto a ring (hash space 0..2^32). Key goes to next node clockwise. Adding/removing node moves only ~1/N of keys. Virtual nodes (each node at multiple ring positions) smooth distribution.
B-tree vs LSM tree — when which?
B-tree: random reads fast, in-place updates, write amplification from page splits. Used by Postgres, MySQL. LSM: writes very fast (append memtable), reads slower (multiple SSTable lookups), compactions. Used by Cassandra, RocksDB. Write-heavy → LSM; read-heavy + range queries → B-tree.
What is MVCC?
Multi-Version Concurrency Control. Each row has multiple versions tagged with transaction id. Readers don't block writers and vice versa. Cost: bloat (old versions) requires VACUUM / garbage collection.
Explain Raft in 60 seconds.
Leader-based consensus. One leader elected by majority vote (heartbeats + term numbers). Clients send writes to leader; leader appends to log; replicates; once majority acks, committed. Followers redirect writes to leader. On leader failure, new election. Used by etcd, Consul, CockroachDB.
What are vector clocks for?
Detect causal vs concurrent updates in distributed systems without coordination. Each node has its own counter; vector = array of all counters. Compare element-wise. Used in Dynamo, Voldemort.
What's a CRDT?
Conflict-Free Replicated Data Type. Data structure where concurrent updates automatically converge without coordination. Examples: G-Counter, OR-Set, LWW-Register. Used by collaboration tools, Riak.
Two-phase commit — why is it problematic?
Blocking: coordinator crash in phase 2 leaves participants in limbo. Slow: synchronous across services. Modern microservices use sagas (compensating actions) instead.
How would you design a global rate limiter?
Per-edge token bucket (low latency, eventual sync). Central Redis cluster as global source of truth (shard by user). Edges allow brief over-limit during partition. Aggregator reconciles every minute. Acceptable: occasional 1-2× limit; never block legitimate traffic.
Sharding strategy choice?
Hash for uniform load. Range for range-query workloads. Consistent hash for rebalancing-friendly. Directory-based for flexibility. Geographic for compliance/locality. Sharding key is hard to change post-launch — design carefully.
How do you handle hot shard?
Sub-shard the hot key (write distribution). Read replicas for read scale. Caching in front. Sharding key change (migrate to better key — painful). Detect early via per-shard QPS metrics.
How would you design the outbox pattern?
Same DB transaction writes business row + event row to outbox table. Separate poller reads unprocessed outbox rows, publishes to Kafka, marks sent. Guarantees at-least-once event delivery atomic with the DB write.
Idempotency-key pattern?
Client generates UUID per logical operation. Server stores key → response. Retries with same key return cached response without re-executing. Critical for at-least-once delivery (queues, retries). Stripe popularised it.
Saga orchestration vs choreography?
Orchestration: central state machine (Temporal, Camunda) calls services. Easier to reason, single point of failure. Choreography: each service reacts to events. Decoupled, harder to debug ("where is this stuck?").
How does Kafka guarantee ordering?
Strict order within a partition. No global order across partitions. Key-based partitioning (hash(key) % N) ensures same key → same partition → ordered for that key.
Kafka vs RabbitMQ vs SQS?
Kafka: append-only log, high throughput, replay-able, consumer-controlled offsets. RabbitMQ: broker-managed queues, complex routing (exchanges), classic task queue. SQS: managed AWS queue, simple, scales to zero. Kafka for event streaming; RabbitMQ/SQS for work distribution.
How do you scale a write-heavy DB?
1. Vertical scale + tune. 2. Read replicas (read-only). 3. Partition by time/tenant. 4. Shard horizontally. 5. Switch to write-optimised DB (Cassandra). Each step is expensive; verify previous insufficient first.
Linearizable vs sequential consistency?
Linearizable: every op appears to take effect instantaneously between invocation and response (real-time order). Sequential: all clients see same global order but not real-time. Linearizable is stronger and more expensive.
CAP vs PACELC?
CAP: during a partition, pick Consistency or Availability. PACELC: else (no partition), pick Latency or Consistency. PACELC captures the always-on trade-off, not just partition behaviour.
How to design read-after-write consistency?
Route a user's reads to leader for a window after their write. Or use sticky session (same replica per user). Or client cache + version checks. Without it, "I clicked Save and it disappeared" UX bug.
Cassandra consistency levels?
Tunable per query: ONE, QUORUM, ALL, LOCAL_QUORUM. W + R > N → strong consistency (read-your-writes). Common: LOCAL_QUORUM for both writes and reads (works within a region).
What is HOL blocking in HTTP/2 and how does HTTP/3 fix it?
HTTP/2 multiplexes streams over one TCP connection. A lost TCP packet stalls all streams (HOL at transport). HTTP/3 runs over QUIC (UDP) with per-stream loss recovery, eliminating transport-layer HOL.
How would you scale Postgres to billions of rows?
Partition by time (range or list partitioning). Read replicas. Connection pooling (PgBouncer). Indexes optimised per access pattern. Then Citus / TimescaleDB for distributed Postgres. Most apps don't need this.
What is the thundering herd problem?
When a cache key expires, many concurrent requests miss and all hit the DB simultaneously. Solutions: single-flight (one fetches, others wait); probabilistic early expiration; jittered TTLs; explicit refresh-ahead.
How would you design Twitter timeline?
Fanout-on-write for normal users (precompute follower feeds). Fanout-on-read for celebs (compute on read). Cache feeds in Redis sorted set per user. ML re-ranks on read. Media on S3+CDN.
How would you design Uber matching?
Geo-index drivers in Redis (S2 cells). Driver app sends location every 4s. On ride request, query nearby cells; pick best driver by ETA+rating. State machine for trip lifecycle. Payment async post-trip.
How to design YouTube?
Upload to S3 → transcoding queue → multiple bitrates → CDN. HLS/DASH adaptive. Metadata in Postgres; views/likes in Cassandra. Recommendation = offline ML + realtime signals.
How to design Dropbox sync?
Chunked files (4 MB blocks). Content-hash addressed (dedup). S3 for blocks. Metadata in Postgres. Delta sync via push notification or polling. Conflict resolution with version vectors.
How would you design a notification service?
Producer publishes intent. Worker fans out to channel-specific senders (push/email/SMS/in-app). Per-user rate limits + quiet hours. Idempotency key. DLQ for failures. Provider fallback.
How would you design ad serving?
RTB auction <100ms per request. Candidate generation (eligible ads). Ranking via ML model (CTR × bid). Frequency capping in Redis. Budget pacing. Click attribution.
How would you design a recommendation engine?
Offline collaborative filtering (matrix factorization or deep learning). Content-based fallback. Feature store. Real-time scoring. Cold-start = popular/trending. Re-rank in real-time per user.
How would you design a distributed file system?
Master (metadata: file → blocks → datanodes; HA via ZooKeeper). Workers store blocks (128 MB, 3× replication). Rack-aware placement. Client → master for locations, then direct to nearest worker.
How does a circuit breaker work?
Tracks failures to a downstream. Closed = normal. After N failures → Open (fail fast, no calls). After cooldown → Half-Open (probe). One success → Closed; failure → Open again. Prevents cascading failure.
Bulkhead pattern?
Isolate failure domains. Separate thread pools / connection pools per downstream. If one downstream becomes slow, only its pool starves — other services unaffected.
What is graceful degradation?
When a dep fails, serve cached/partial data instead of erroring. Example: recommendation service down → show popular items. Stay useful even when degraded.
How would you design a payment system safely?
Idempotency key per checkout. Append-only ledger for audit. Webhook from provider for async outcomes. Reconciliation jobs nightly. Tokenize cards via provider (PCI scope minimisation). Fraud rules + ML on transaction stream.
How would you debug a global latency spike?
Check dashboards: latency by region, service, endpoint. Identify scope (region? all? subset?). Check recent deploys, infra events (provider status pages). Trace a representative slow request via distributed tracing. Common: DB slow query, downstream service degradation, GC pause, network change, deploy bug.
What's "cell-based architecture"?
Divide infrastructure into independent cells, each hosting a subset of users. Blast radius of any failure is one cell. AWS uses this internally. Trade-off: extra routing layer, rebalancing complexity.
How to handle slow consumer in Kafka?
Scale consumers up to partition count. Batch messages. Process async (decouple ack from work). If consumer fundamentally slow, add more partitions (one-time rebalance) or use parallel processing per partition (carefully — order preserved only per partition).
How does Spanner achieve external consistency globally?
TrueTime: atomic clocks + GPS in every datacenter; bounded clock uncertainty ε. Commits wait out ε before responding. Guarantees: any commit with timestamp T returns after T-real-time elapses. Linearizable global ordering.
One-sentence summary of how to approach system design?
"Clarify ruthlessly, estimate roughly, sketch clearly, justify trade-offs, and dive deep into one piece rather than half-cover five."

66.X Worked Designs — Code Reference

URL Shortener — Base62 + FastAPI

from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
import redis, string, random

app = FastAPI()
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
CHARS = string.ascii_letters + string.digits  # 62 chars

def base62_encode(n: int) -> str:
    result = ""
    while n:
        result = CHARS[n % 62] + result
        n //= 62
    return result or CHARS[0]

def generate_short_code(url: str) -> str:
    # Use Redis INCR for monotonic ID
    seq_id = r.incr("url:seq")
    code = base62_encode(seq_id)
    r.setex(f"url:{code}", 86400 * 30, url)
    return code

@app.post("/shorten")
def shorten(url: str) -> dict:
    # Check existing (dedup by URL hash)
    import hashlib
    url_hash = hashlib.sha256(url.encode()).hexdigest()[:16]
    existing = r.get(f"hash:{url_hash}")
    if existing:
        return {"short_code": existing, "url": url}
    code = generate_short_code(url)
    r.set(f"hash:{url_hash}", code)
    return {"short_code": code, "url": url}

@app.get("/{code}")
def redirect(code: str):
    url = r.get(f"url:{code}")
    if not url:
        raise HTTPException(status_code=404, detail="Link not found or expired")
    r.incr(f"url:{code}:clicks")  # analytics
    return RedirectResponse(url=url, status_code=302)

Chat Service — WebSocket + Redis Pub/Sub

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import redis.asyncio as aioredis
import asyncio, json

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.rooms: dict[str, list[WebSocket]] = {}

    async def connect(self, ws: WebSocket, room: str):
        await ws.accept()
        self.rooms.setdefault(room, []).append(ws)

    def disconnect(self, ws: WebSocket, room: str):
        self.rooms.get(room, []).remove(ws)

    async def broadcast(self, room: str, message: str, sender: WebSocket = None):
        for ws in self.rooms.get(room, []):
            if ws is not sender:
                await ws.send_text(message)

mgr = ConnectionManager()
redis_pub = aioredis.Redis(host="localhost")
redis_sub = aioredis.Redis(host="localhost")

@app.websocket("/ws/{room}/{user_id}")
async def websocket_endpoint(ws: WebSocket, room: str, user_id: str):
    await mgr.connect(ws, room)
    sub = redis_sub.pubsub()
    await sub.subscribe(f"chat:{room}")

    async def listen_redis():
        async for msg in sub.listen():
            if msg["type"] == "message":
                await ws.send_text(msg["data"])

    asyncio.create_task(listen_redis())
    try:
        while True:
            data = await ws.receive_text()
            payload = json.dumps({"user": user_id, "text": data})
            await redis_pub.publish(f"chat:{room}", payload)
    except WebSocketDisconnect:
        mgr.disconnect(ws, room)

News Feed Fanout — Write Path

import redis, json, time

r = redis.Redis(host="localhost", port=6379)

def post_tweet(user_id: int, content: str) -> int:
    tweet_id = r.incr("tweet:seq")
    tweet = {"id": tweet_id, "user_id": user_id, "content": content, "ts": int(time.time())}
    r.set(f"tweet:{tweet_id}", json.dumps(tweet))

    # Fan-out to followers (write-to-fan-out for small follower count)
    followers = r.smembers(f"followers:{user_id}")
    pipe = r.pipeline()
    for follower_id in followers:
        # Each follower has a sorted set timeline, score = timestamp
        pipe.zadd(f"timeline:{follower_id.decode()}", {str(tweet_id): tweet["ts"]})
        pipe.zremrangebyrank(f"timeline:{follower_id.decode()}", 0, -1001)  # keep last 1000
    pipe.execute()
    return tweet_id

def get_feed(user_id: int, page: int = 0, per_page: int = 20) -> list:
    start = page * per_page
    end   = start + per_page - 1
    tweet_ids = r.zrevrange(f"timeline:{user_id}", start, end)
    pipe = r.pipeline()
    for tid in tweet_ids:
        pipe.get(f"tweet:{tid.decode()}")
    return [json.loads(t) for t in pipe.execute() if t]

Distributed Lock — Redis SETNX

import redis, uuid, time
from contextlib import contextmanager

r = redis.Redis(host="localhost", port=6379)

@contextmanager
def distributed_lock(resource: str, ttl: int = 30, retry: int = 3, wait: float = 0.1):
    lock_key = f"lock:{resource}"
    lock_val = str(uuid.uuid4())
    acquired = False
    for _ in range(retry):
        acquired = r.set(lock_key, lock_val, nx=True, ex=ttl)
        if acquired:
            break
        time.sleep(wait)
    if not acquired:
        raise Exception(f"Could not acquire lock on {resource}")
    try:
        yield
    finally:
        # Release only if still our lock (Lua for atomicity)
        script = """
        if redis.call("get",KEYS[1]) == ARGV[1] then
            return redis.call("del",KEYS[1])
        else return 0 end
        """
        r.eval(script, 1, lock_key, lock_val)

# Usage
# with distributed_lock("payment:order_99"):
#     process_payment("order_99")

Bloom Filter — Duplicate URL Detection

import math, mmh3
from bitarray import bitarray

class BloomFilter:
    def __init__(self, n: int, fp_rate: float = 0.01):
        m = int(-n * math.log(fp_rate) / (math.log(2) ** 2))
        k = int((m / n) * math.log(2))
        self.m = m
        self.k = k
        self.bits = bitarray(m)
        self.bits.setall(0)

    def add(self, item: str):
        for seed in range(self.k):
            idx = mmh3.hash(item, seed) % self.m
            self.bits[idx] = 1

    def __contains__(self, item: str) -> bool:
        return all(self.bits[mmh3.hash(item, s) % self.m] for s in range(self.k))

# URL deduplication for web crawler
seen = BloomFilter(n=10_000_000, fp_rate=0.001)  # 10M URLs, 0.1% FP rate
seen.add("https://example.com/page1")
print("https://example.com/page1" in seen)  # True
print("https://example.com/page2" in seen)  # False