Updated Oct 2026

Design anything, and defend every box you draw

A step-by-step design process, the numbers worth knowing by heart, the building blocks, 18 classic problems with sketch answers, and AI-era systems (RAG, LLM serving, agents).

Part 1

Design process

Use the same six steps for every design, so nothing important gets skipped and every decision has a reason behind it.

  1. Clarify requirements

    Functional: list 3–5 core features, then pick the 2–3 that matter most.

    Non-functional: scale (DAU, data size), latency targets, availability vs. consistency, durability, read-heavy or write-heavy, global or single region.

    State what is out of scope (auth, billing, analytics) so the design stays focused.

  2. Back-of-envelope estimates

    Compute only numbers that change the design: peak QPS, storage over 5 years, bandwidth. Then state the consequence: "40K reads/s means a cache is mandatory; 2 TB/year fits in one sharded Postgres cluster."

  3. API and data model

    Write 3–5 endpoint signatures (POST /v1/urls {longUrl} → {shortCode}). List the entities and the main access patterns, then choose storage from the access patterns, not from habit.

  4. High-level design

    Draw the simplest design that satisfies every functional requirement. Walk one request through it end to end for each core feature. Don't optimize yet.

  5. Deep dives

    Pick 2–3 hard parts: the hot path at scale, the consistency-sensitive write, a failure mode. List the options, compare them, choose one and write down why.

  6. Review

    Recap the trade-offs you made, name the bottleneck that breaks first at 10× scale, and mention monitoring (what you'd alert on) and how the design evolves.

Good habits

  • Every choice comes with a trade-off and a number.
  • Failure modes are considered up front, not after an outage.
  • Boring, proven tech by default, known deeply (Postgres, Kafka, Redis, DynamoDB).
  • Simplify: "At 200 QPS one Postgres primary is enough."

Common mistakes

  • Drawing boxes before agreeing on requirements.
  • Naming tech without saying why.
  • Microservices, Kafka and sharding for a 100 QPS app.
  • A single point of failure nobody mentions.
Clarifying questions to have readyA reusable list so step 1 never stalls
  • Who are the users and how many? DAU, peak concurrency, geographic spread.
  • What is the read:write ratio? What is the size of a typical object?
  • What latency matters, and for which operation? (p99 < 200 ms for reads?)
  • Is it acceptable to show stale data? For how long? Can we ever lose a write?
  • Mobile, web, or both? Do we need offline support or real-time updates?
  • Is ordering important (chat messages, ledger entries)?
  • Any compliance limits (PII, data residency, retention)?
The trade-off vocabularyPairs that come up in almost every design
Trade-offPick the left when…
Consistency vs availabilityMoney, inventory, auth. Wrong data costs more than downtime.
Latency vs throughputUser-facing request paths. Batch and buffer for background work.
Push vs pullFew consumers, low fan-out, real-time needs.
Normalize vs denormalizeWrite-heavy data with many relationships; denormalize for hot reads.
Sync vs asyncThe caller needs the result now. Otherwise put a queue in between.
Strong vs eventualRead-after-write is visible to the user (profile edit, payment status).
Build vs buy (managed)The component is your core differentiator. Otherwise default to managed services.
Part 2

Numbers & estimation

Use orders of magnitude, not exact figures. What matters is that the reasoning is sound and that you act on the result.

Latency, on a log scale

Bar length is log₁₀ of the latency in nanoseconds. Values are rough modern figures; memorize the ratios, not the digits.

Conversion shortcutsDo the math in your head in seconds
FactValue
Seconds per day86,400 ≈ 10⁵
1 M requests/day≈ 12 QPS
100 M requests/day≈ 1,200 QPS
1 B requests/day≈ 12,000 QPS
Peak vs average2–3× (up to 10× for events)
2¹⁰ / 2²⁰ / 2³⁰ / 2⁴⁰ / 2⁵⁰KB / MB / GB / TB / PB
UUID / bigint / timestamp16 B / 8 B / 8 B
Short text post with metadata~1 KB
Compressed photo200 KB – 2 MB
1 min of 1080p video (~5 Mbps)~40 MB
Single-node capacity rules of thumbWhen do you actually need to scale out?
ComponentRoughly handles
Stateless app server1K–10K RPS
Postgres / MySQL primary5K–20K simple writes/s, more reads; ~10 TB comfortable
Redis node100K+ ops/s, memory-bound (~100 GB)
Kafka broker100s of MB/s; cluster: millions msgs/s
Cassandra / DynamoDBScales linearly; ~10K ops/s per node
WebSocket server50K–1M idle connections (tuned)
NIC10–25 Gbps ≈ 1–3 GB/s

Make the conclusion explicit: "10K writes/s is near one Postgres primary's limit, so I'll shard by user_id or pick a store that partitions natively."

Availability ninesDowntime budgets and why they compound
SLAPer yearPer month
99%3.65 days7.3 h
99.9%8.77 h43.8 min
99.99%52.6 min4.38 min
99.999%5.26 min26 s

Components in series multiply: three 99.9% services in a chain give about 99.7%. Redundant copies in parallel: 1 − (1 − a)ⁿ.

Back-of-envelope calculator

Example values: a Twitter-like service with 100 M DAU posting twice a day.

Part 3

Fundamentals

The building blocks every design is made of. For each one, know what it is, when to use it, and how it fails.

The default skeleton. Most designs are this picture plus two or three problem-specific components. Dashed lines are static or out-of-band paths.

Scaling and statelessnessVertical vs horizontal, where state livescore
  • Vertical (bigger box): simple and surprisingly far-reaching, but has a ceiling and is a single point of failure.
  • Horizontal (more boxes): requires stateless services. Move sessions to Redis or signed tokens, and files to object storage.
  • Autoscale on a leading signal (CPU, queue depth, concurrent requests) with cooldowns to avoid flapping.
  • The stateful tier (database, cache, queue) is always the hard part. Say where state lives and how it scales.
Load balancingL4 vs L7, algorithms, health checkscore
  • L4 routes on IP/port (fast, protocol-agnostic). L7 routes on HTTP path, headers or cookies, terminates TLS, and can retry.
  • Algorithms: round robin, weighted, least connections, least response time, consistent hashing (for cache affinity or sticky shards).
  • Active health checks plus outlier ejection. Run the LB itself redundantly (active-passive with a floating IP, or managed).
  • Global: DNS-based or anycast GSLB routes users to the nearest healthy region.
  • Sticky sessions hurt even load distribution; prefer stateless services.
CachingPatterns, eviction, stampedes, invalidationcore

Where

Browser → CDN → API gateway → in-process (local) → distributed (Redis/Memcached) → database buffer pool.

Write patterns

PatternHowUse when
Cache-asideApp reads cache; on miss reads DB and fills cacheDefault choice; tolerant of cache failure
Read-throughCache library loads from DB on missSame as above, less app code
Write-throughWrite cache and DB synchronouslyRead-after-write must hit cache
Write-behindWrite cache, flush to DB asynchronouslyWrite-heavy counters; risk of loss
Write-aroundWrite DB only; cache fills on readData rarely re-read soon after write

Eviction

LRU (recency), LFU (frequency, better for skewed popularity), TTL (bounded staleness). Redis uses approximate LRU/LFU by sampling.

Failure modes

  • Stampede (many misses for one hot key): request coalescing / single-flight, locks, jittered TTLs, probabilistic early refresh.
  • Hot key: replicate the key across nodes with a suffix (key#1..n), or add a small local cache.
  • Invalidation: delete on write (not update) to avoid races; CDC-driven invalidation for multiple writers.
  • Penetration (lookups of nonexistent keys): cache negatives, or front the store with a Bloom filter.
Key insightState the hit ratio you expect and what happens to the database when the cache is cold or down.
Choosing a databaseSQL vs NoSQL, B-tree vs LSMcore
TypeExamplesGood at
RelationalPostgres, MySQL, Aurora, SpannerTransactions, joins, constraints, flexible queries
Key-valueRedis, DynamoDBSingle-key lookups at huge scale
Wide-columnCassandra, ScyllaDB, BigtableWrite-heavy time-ordered data, partition + sort key
DocumentMongoDB, FirestoreNested, schema-flexible aggregates
SearchElasticsearch, OpenSearchFull text, faceting, relevance
Time-seriesPrometheus, InfluxDB, TimescaleDBMetrics, downsampling, retention
GraphNeo4jMulti-hop relationship queries
Vectorpgvector, Pinecone, MilvusNearest-neighbor over embeddings
  • B-tree (Postgres, MySQL): in-place updates, fast reads and range scans.
  • LSM tree (Cassandra, RocksDB): append to memtable and SSTables, then compact. Fast writes; reads may check several files (Bloom filters help).
  • Indexes speed up reads and slow down writes. Composite index column order follows the query's equality columns first, then range.
  • Isolation levels: read committed (default in Postgres), repeatable read / snapshot, serializable. Know the anomalies: lost update, write skew.
Key insight"Postgres until proven otherwise" is a strong default. Justify any NoSQL choice with an access pattern or a scale number.
ReplicationLeader-follower, multi-leader, leaderless quorums
  • Single leader: writes go to the primary, reads scale on replicas. Async replication means replica lag and possible loss of recent writes on failover.
  • Multi-leader: one writable leader per region. Needs conflict resolution (last-writer-wins, CRDTs, app merge).
  • Leaderless (Dynamo, Cassandra): write to W of N replicas, read from R. If R + W > N, a read overlaps the latest write. Read repair and hinted handoff fix divergence.
  • Read-your-writes under lag: route a user's reads to the primary for a few seconds after they write, or track the replication position (LSN).
  • Failover dangers: split brain (two primaries). Use consensus-based election and fencing.
Partitioning and shardingShard keys, consistent hashing, hot spotscore
  • Range partitioning keeps scans efficient but creates hot spots on sequential keys (timestamps).
  • Hash partitioning spreads load evenly but loses range queries.
  • Consistent hashing: keys and nodes on a ring; adding a node moves only ~1/N of keys. Virtual nodes smooth out the distribution.
  • Pick the shard key from the dominant query: user_id for user-scoped data, conversation_id for chat.
  • Celebrity/hot partitions: split the key with a suffix, or handle hot entities on a separate path.
  • Cross-shard queries need scatter-gather or a global secondary index (updated asynchronously).
  • Resharding: double the shard count and move half the data, or use many logical shards mapped to fewer physical nodes from day one.
CAP, PACELC and consistency modelsWhat "consistent" actually meanscore
  • CAP: during a network Partition you choose Consistency or Availability. Partitions will happen, so the real choice is CP vs AP.
  • PACELC: Else (no partition), you trade Latency against Consistency. This is the trade-off you make every day.
ModelGuaranteeExample need
LinearizableBehaves like one copy; reads see the latest writeLocks, leader election, balances
SequentialAll see the same order, not necessarily real-timeReplicated logs
CausalCause before effect (reply after message)Comments, chat
Read-your-writesYou see your own updatesProfile edits
Monotonic readsNever go back in timeFeeds, timelines
EventualConverges if writes stopLike counts, view counts
Message queues and event streamsQueue vs log, delivery semantics, outboxcore
  • Queue (SQS, RabbitMQ): a message goes to one consumer and is deleted on ack. Good for task distribution.
  • Log (Kafka, Kinesis, Pulsar): an append-only, partitioned, retained log. Many consumer groups read independently and can replay.
  • Ordering is only guaranteed within a partition. Choose the partition key to group events that must stay ordered (order_id).
  • Delivery: at-most-once (may lose), at-least-once (may duplicate, the usual default), exactly-once = at-least-once + idempotent consumer or transactional processing.
  • Idempotency: dedupe table keyed by message ID, or naturally idempotent writes (upsert, set-to-value).
  • Failure handling: retries with backoff, then a dead-letter queue. Watch consumer lag; backpressure by bounding queue size.
  • Transactional outbox: write the business row and an outbox row in one DB transaction; a relay (or CDC like Debezium) publishes it. Solves "DB committed but event lost".
Key insightExplain what happens if the consumer crashes after processing but before committing its offset. Answer: it reprocesses, so the handler must be idempotent.
Distributed transactions2PC, sagas, idempotency keys
  • 2PC: coordinator asks all participants to prepare, then commit. Strong but blocking if the coordinator dies; rare across services.
  • Saga: a sequence of local transactions, each with a compensating action (refund, release seat). Choreography uses events; orchestration uses a central workflow (Temporal, Step Functions). Orchestration is easier to reason about.
  • Sagas give eventual consistency. Design visible intermediate states (PENDING, RESERVED).
  • Idempotency key: the client sends a unique key per logical operation; the server stores key → result and returns the stored result on retry.
Consensus, leases and locksRaft, etcd/ZooKeeper, fencing tokens
  • Raft / Paxos: a majority (quorum) agrees on a replicated log. Tolerates f failures with 2f + 1 nodes.
  • You rarely implement it. Use etcd, ZooKeeper or Consul for leader election, config and service discovery.
  • Leases: a lock with a TTL that the holder must renew. A paused process (GC, network) can lose its lease without knowing.
  • Fencing tokens: a monotonically increasing number issued with each lock; storage rejects writes with an older token. This makes distributed locks safe.
  • A Redis lock (SET key val NX PX 30000) is fine for efficiency (avoid duplicate work), not for correctness.
Unique ID generationUUIDv4, UUIDv7, Snowflake
  • UUIDv4: random, no coordination, but poor B-tree locality (random inserts).
  • UUIDv7: timestamp-prefixed, sortable, index-friendly. Today's sensible default.
  • Snowflake (64-bit): 1 sign + 41 bits ms timestamp (~69 years) + 10 bits machine ID + 12 bits sequence (4,096 IDs/ms/machine). Watch for clock skew going backwards.
  • Ticket server / DB sequence: simple, but a bottleneck. Hand out ranges (blocks of 1,000) to reduce calls.
API design and real-time transportREST, gRPC, GraphQL, WebSocket, SSE
  • REST: public APIs, cacheable. gRPC: internal service-to-service, typed, streaming. GraphQL: client-shaped reads across many entities; watch N+1 queries.
  • Pagination: cursor-based (?after=opaque) is stable under inserts and fast; offset is simple but slow and shifts.
  • Version in the path or header; make writes idempotent; return 429 with Retry-After.
TransportDirectionUse for
Short pollingClient pullsRare updates, simplest
Long pollingClient pulls, server holdsFallback where sockets are blocked
SSEServer → clientNotifications, LLM token streaming
WebSocketBidirectionalChat, collaboration, games
WebhooksServer → serverThird-party callbacks
Rate limiting algorithmsToken bucket and friends
AlgorithmBehaviorTrade-off
Token bucketRefill r tokens/s up to capacity bAllows bursts; most common
Leaky bucketQueue drains at a fixed rateSmooth output, adds latency
Fixed windowCounter per minuteCheap; 2× burst at window edges
Sliding logStore every timestampExact; memory-heavy
Sliding window counterWeighted blend of current and previous windowAccurate enough, cheap
Blob storage and CDNPre-signed URLs, multipart, edge caching
  • Never stream large files through your API servers. Issue a pre-signed URL so the client uploads directly to S3/GCS.
  • Multipart / resumable uploads for big files; process with an event on upload completion (thumbnailing, transcoding).
  • CDN for static and media content: pull-through caching, cache keys, TTLs, invalidation by versioned URLs (app.3f9c.js).
  • Object stores are cheap and 11-nines durable, with higher latency (tens of ms). Use storage tiers for cold data.
Search and geospatial indexesInverted index, geohash, quadtree, H3
  • Inverted index: term → list of document IDs. Tokenize, normalize, stem; rank with BM25 plus business signals.
  • Keep the search index in sync from the source of truth via CDC or events. Accept seconds of lag.
  • Geohash: encodes lat/long into a string prefix; nearby points share prefixes. Query the cell plus its 8 neighbors.
  • Quadtree: splits dense areas adaptively. S2 / H3: hierarchical cells used by Google and Uber.
  • Postgres with PostGIS or Redis GEO commands covers most interview scales.
Reliability patternsTimeouts, retries, circuit breakers, load sheddingcore
  • Every network call gets a timeout. Budget them down the call chain.
  • Retries with exponential backoff plus jitter, only for idempotent operations, with a retry budget to avoid retry storms.
  • Circuit breaker: stop calling a failing dependency; probe periodically (half-open).
  • Bulkheads: separate pools per dependency so one slow service can't exhaust all threads.
  • Load shedding and graceful degradation: drop low-priority work first, serve stale cache, hide non-critical widgets.
  • Hedged requests: send a second request after the p95 delay to cut tail latency.
  • Multi-AZ by default; multi-region active-passive or active-active when the SLA demands it. Cell-based architecture limits blast radius.
Observability and SLOsMetrics, logs, traces, error budgets
  • Metrics: RED for services (Rate, Errors, Duration), USE for resources (Utilization, Saturation, Errors).
  • Structured logs with request IDs; distributed tracing (OpenTelemetry) across services.
  • SLI (measured, e.g. p99 latency) → SLO (target, 99.9% under 300 ms) → error budget (what you can spend on risky deploys).
  • Alert on symptoms users feel (SLO burn rate), not on every cause.
Security basicsAuthN, AuthZ, tokens, multi-tenancy
  • AuthN via OAuth 2.0 / OIDC. Short-lived JWT access tokens plus refresh tokens; server-side sessions when you need instant revocation.
  • AuthZ: RBAC or ABAC, enforced at the service, not only at the gateway.
  • TLS everywhere, encryption at rest, secrets in a vault (KMS), least-privilege IAM.
  • Multi-tenant isolation: tenant_id on every row and query, row-level security, per-tenant rate limits, noisy-neighbor protection.
Batch and stream processingSpark, Flink, windows, watermarks
  • Batch (Spark, warehouse SQL) for large periodic jobs; streaming (Flink, Kafka Streams) for low-latency aggregates.
  • Windows: tumbling, sliding, session. Watermarks decide when late events are too late.
  • Lambda architecture (batch + speed layer) vs Kappa (stream only, replay the log to recompute). Kappa is simpler when the log is retained.
  • Lakehouse: Parquet files with a table format (Iceberg, Delta) on object storage, queried by many engines.
Probabilistic data structuresBloom filter, HyperLogLog, Count-Min Sketch
  • Bloom filter: "definitely not present" or "maybe present". Used for crawler dedupe, LSM reads, username checks.
  • HyperLogLog: count distinct elements (unique visitors) in ~12 KB with ~1% error.
  • Count-Min Sketch: approximate frequency counts for heavy hitters (top-K trending).
Part 4

Classic problems

Sketch answers, not full solutions. Try each one yourself first, then compare against the notes. core marks the ones asked most often.

URL shortenerbit.ly · read-heavy, key generationcore

Numbers

100 M new URLs/day ≈ 1.2K writes/s; 100:1 reads → ~120K reads/s peak-ish. 500 B/row × 36 B rows over 1 year ≈ 18 TB.

Design

POST /urls → API → ID generator → base62(id) → DB (code → long_url) GET /{code} → CDN/edge → API → Redis → DB → 301/302 redirect
  • 7 base62 characters = 62⁷ ≈ 3.5 trillion codes.
  • Key generation options: hash + collision check; counter + base62 (predictable, so add a shuffle); pre-generated key ranges handed to each server (no coordination).
  • KV store (DynamoDB/Cassandra) or sharded Postgres by code. Cache hot codes; most traffic hits a small set.
  • 301 (cached by browsers, fewer hits, lose analytics) vs 302 (every click reaches you).

Deep dives

Analytics via async click events to Kafka; custom aliases; expiry; abuse and malware scanning.

Distributed rate limiterAPI gateway · shared counters, low latencycore

Clarify

Limit per user, IP or API key? Hard or soft limit? Added latency budget (< 1 ms)? Fail open or closed?

Design

  • Runs as middleware in the gateway. Rules from a config service, cached locally.
  • Token bucket state in Redis: one atomic Lua script reads tokens, refills by elapsed time, decrements, sets TTL.
  • Shard Redis by key. Return 429 plus X-RateLimit-Remaining and Retry-After.
  • To cut Redis round-trips: local token pre-allocation (take 10 tokens at a time) at the cost of slight overshoot.
  • If Redis is down: fail open for most APIs, fail closed for expensive or abuse-prone ones.
Key insightMulti-region: accept per-region limits (limit/regions) with async sync, rather than a global synchronous counter.
LRU cacheIn-process and distributed · O(1) get/putcore
LRU vs LFU playgroundRun both caches on the same requests and watch what each one evicts.

In-process

Hash map from key to node, plus a doubly linked list ordered by recency. Get moves the node to the head; put inserts at the head and evicts the tail when full. Both O(1).

class LRUCache:
    def __init__(self, cap):
        self.cap, self.map = cap, {}
        self.head, self.tail = Node(), Node()      # sentinels
        self.head.next, self.tail.prev = self.tail, self.head

    def get(self, key):
        node = self.map.get(key)
        if not node: return -1
        self._unlink(node); self._push_front(node)
        return node.val

    def put(self, key, val):
        if key in self.map: self._unlink(self.map[key])
        node = Node(key, val); self.map[key] = node; self._push_front(node)
        if len(self.map) > self.cap:
            lru = self.tail.prev; self._unlink(lru); del self.map[lru.key]

Thread safety: a single lock is simplest; segment the map into N shards, each with its own lock and list, for throughput. Java: LinkedHashMap(accessOrder=true); Python: OrderedDict.move_to_end.

Distributed cache

  • Consistent hashing with virtual nodes to place keys across cache nodes; client-side or proxy routing (twemproxy, mcrouter).
  • Each node runs LRU locally. Replicate for availability (primary/replica) or accept misses on node loss.
  • Approximate LRU (sample 5 keys, evict the oldest) avoids list overhead, as in Redis.
  • Hot keys, stampede and invalidation: see Fundamentals → Caching.
Distributed key-value storeDynamo-style · partitioning, quorums, repair
  • Partition with consistent hashing; replicate each key to the next N nodes on the ring.
  • Tunable quorums: N=3, W=2, R=2 for strong-ish reads; W=1 for fast writes.
  • Conflict handling: vector clocks or last-writer-wins with timestamps.
  • Membership and failure detection via gossip; hinted handoff during brief outages; Merkle trees for anti-entropy repair.
  • Storage engine: LSM tree (commit log → memtable → SSTables, compaction, Bloom filters).
Chat / messaging systemWhatsApp, Slack · real-time delivery, ordering, presencecore

Clarify

1:1 and groups (max size?), online/offline delivery, read receipts, media, history sync across devices, end-to-end encryption?

Numbers

500 M DAU × 40 msgs = 20 B msgs/day ≈ 230K msgs/s average. Tens of millions of concurrent WebSocket connections.

Design

Client ⇄ WebSocket gateway (stateful) → Chat service → Message store (Cassandra, partition = conversation_id, sort = msg_id) ↘ Session registry (Redis: user → gateway node) Recipient online → route to their gateway → push Recipient offline → push notification service (APNs/FCM) + fetch on reconnect
  • Message IDs: per-conversation sequence or Snowflake/UUIDv7, so ordering is defined per conversation.
  • Delivery states: sent (server ack) → delivered (device ack) → read. Client retries with a client-generated ID to dedupe.
  • Sync: each device keeps a cursor of the last message seen; on reconnect it pulls everything after it.
  • Groups: small groups fan out on write to each member's inbox; huge channels (Slack, Discord) fan out on read.
  • Presence: heartbeats with TTL in Redis; publish changes only to contacts who are online, and throttle.
  • Gateways communicate through a pub/sub layer (Redis pub/sub, Kafka, or direct RPC via the registry).
Key insightExplain what happens when a gateway node dies: clients reconnect to another node, re-register, and pull missed messages by cursor. No message is lost because the store, not the socket, is the source of truth.
Distributed message queueDesign Kafka · partitions, replication, consumer groupscore
  • Topic → partitions; each partition is an append-only log split into segment files. Sequential disk I/O plus OS page cache plus zero-copy sendfile make it fast.
  • Producers pick a partition by key hash (ordering per key) or round robin. Batch and compress.
  • Replication: each partition has a leader and followers; the in-sync replica set (ISR) decides when a write is committed. acks=all + min.insync.replicas=2 for durability.
  • Consumers in a group split partitions among themselves; they commit offsets. Max parallelism = partition count.
  • Metadata and leader election via a controller quorum (KRaft, formerly ZooKeeper).
  • Retention by time or size; log compaction keeps the latest value per key.
  • Exactly-once: idempotent producer (sequence numbers per partition) plus transactions across partitions and offsets.

Queue vs log trade-off

Need per-message ack, delays and visibility timeouts? Use SQS-style. Need replay, multiple independent consumers and high throughput? Use a log.

News feed / timelineTwitter, Instagram · fan-out on write vs readcore
  • Fan-out on write (push): on post, append the post ID to each follower's feed cache (Redis list). Fast reads; costly for users with millions of followers.
  • Fan-out on read (pull): build the feed at read time from followed users' recent posts. Cheap writes, slow reads.
  • Hybrid: push for normal users; pull celebrity posts at read time and merge.
  • Feed cache stores only IDs (keep the last ~800); hydrate posts and authors from caches.
  • Ranking: candidate generation → ML scoring → re-rank for diversity. Cursor pagination.
  • Inactive users: skip fan-out to them; rebuild their feed lazily on login.
Notification systemPush, SMS, email · multi-channel, reliable, rate-limited
Producers → Notification API (validate, idempotency key) → Kafka (per channel) → Workers: user prefs + templates + rate limits → providers (APNs, FCM, Twilio, SES) → delivery status events → status store + analytics
  • Separate queues per channel and priority so a flood of marketing email doesn't delay OTP SMS.
  • Respect preferences, quiet hours and per-user frequency caps. Dedupe with idempotency keys.
  • Retries with backoff for provider errors; fall back to a secondary provider; DLQ for poison messages.
  • Scheduled notifications: a delay queue or time-bucketed table scanned by a scheduler.
Typeahead / autocompleteGoogle search box · < 100 ms, top-K per prefix
  • Trie where each node stores the precomputed top-K completions; lookup is O(prefix length).
  • Build offline from query logs (hourly/daily batch), with time decay for trending terms; ship read-only snapshots to serving nodes.
  • Shard by prefix range; replicate heavily since it's read-only. Cache popular prefixes at the CDN/browser.
  • Client debounces keystrokes (~100–150 ms) and caches responses per prefix.
  • Filter offensive terms; personalize by blending global and user history.
Web crawlerBillions of pages · politeness, dedupe, frontier
Seed URLs → URL frontier (priority + per-host queues) → fetchers (DNS cache, robots.txt) → content store (S3) → parser → link extractor → URL filter + seen-set (Bloom) → frontier
  • Politeness: one queue per host with a delay; respect robots.txt and crawl-delay.
  • Dedupe URLs (normalize, Bloom filter) and content (SimHash for near-duplicates).
  • Priority by PageRank/freshness; recrawl based on change frequency.
  • Avoid traps (infinite calendars) with depth and URL-length limits. Checkpoint the frontier for crash recovery.
Ride-sharing / proximity serviceUber, Yelp · location updates, nearby search, matchingcore
  • Drivers send location every ~4 s: 1 M active drivers ≈ 250K writes/s. Keep it in memory (Redis GEO or an in-memory geo index keyed by H3/geohash cell), not in the main DB.
  • Nearby search: query the rider's cell and neighbors, filter by distance and availability, rank by ETA.
  • Matching: offer to one driver at a time with a timeout. Use a lock or conditional write on driver status so two riders can't get the same driver.
  • Trip state machine in a durable DB; location history streamed to Kafka for analytics and billing.
  • Shard by region/city; dense cities need finer cells. Surge pricing from supply/demand per cell.

Yelp variant: businesses are static and read-heavy, so a precomputed geohash/quadtree index cached in memory is enough.

Video streamingYouTube, Netflix · upload pipeline, adaptive bitrate, CDN
  • Upload via pre-signed URL (resumable) → object store → event triggers a transcoding DAG (split into chunks, encode in parallel into multiple resolutions and codecs).
  • Package as HLS/DASH segments (2–6 s) with manifests. The player picks bitrate adaptively.
  • CDN serves almost all bytes; popular content is pre-positioned, long tail is pulled from origin.
  • Metadata in a SQL/NoSQL store; view counts via async aggregation (approximate is fine).
  • Cost control: encode long-tail videos lazily at fewer resolutions.
File sync / storageDropbox, Google Drive · chunking, dedupe, sync conflicts
  • Split files into ~4 MB chunks addressed by content hash; upload only changed chunks; dedupe identical chunks across users.
  • Metadata service (SQL): file → ordered list of chunk hashes, versions, permissions.
  • Clients learn about changes via long polling / WebSocket notifications, then pull the new metadata and missing chunks.
  • Conflicts: if two devices edit the same base version, keep both ("conflicted copy") rather than silently merge.
Ticket bookingTicketmaster · contention, holds, flash crowdscore
  • Seat states: AVAILABLE → HELD (with expiry) → BOOKED. Hold via conditional update: UPDATE seats SET status='HELD', hold_until=… WHERE id=? AND status='AVAILABLE', or Redis SET NX EX with DB as the source of truth.
  • Holds expire after ~10 min; a sweeper or TTL releases them.
  • Payment with an idempotency key; confirm booking only on payment success (saga with compensation).
  • Flash crowds: a virtual waiting room (queue tokens) admits users at a controlled rate; cache the event page at the CDN; seat map availability can be slightly stale.
Key insightStrong consistency on the seat row is non-negotiable; everything else (browse, search, seat map) can be eventually consistent and cached.
Payment systemStripe-like · exactly-once money movement, ledger
  • Idempotency key per payment request; store request → result before calling the processor.
  • Double-entry ledger: every transfer is two immutable entries (debit + credit) that sum to zero. Never update balances in place without the ledger.
  • Payment state machine: CREATED → AUTHORIZED → CAPTURED → SETTLED / FAILED / REFUNDED. Persist transitions.
  • External PSP calls can time out with unknown results: retry with the same idempotency key, and reconcile daily against PSP settlement files.
  • Relational DB with serializable or carefully locked transactions; outbox for downstream events.
Collaborative editorGoogle Docs · concurrent edits, OT vs CRDT
  • OT (operational transformation): a central server orders operations and transforms concurrent ones. Google Docs uses this.
  • CRDT (Yjs, Automerge): operations commute, so replicas converge without a central order. Works offline and peer-to-peer; more metadata.
  • One document session lives on one server (route by doc_id) holding the in-memory state; clients connect via WebSocket.
  • Persist an operation log plus periodic snapshots; cursors and presence are ephemeral.
LeaderboardGaming · real-time rank for millions
  • Redis sorted set: ZINCRBY to update, ZREVRANK for a player's rank, ZREVRANGE for top N. All O(log n).
  • Persist scores in a DB; the sorted set can be rebuilt.
  • Very large boards: shard by score range, or compute exact rank only for the top and approximate percentiles for the rest.
  • Time-bounded boards (weekly): one key per period with TTL.
Distributed job schedulerCron at scale · at-least-once, no double runs
  • Jobs table with next_run_at indexed. Schedulers poll due jobs with SELECT … FOR UPDATE SKIP LOCKED or partition jobs by hash across scheduler nodes.
  • Enqueue due jobs to a queue; workers execute and report status. Visibility timeout and heartbeats detect dead workers.
  • Jobs must be idempotent since at-least-once is the realistic guarantee.
  • Retries with backoff, max attempts, DLQ; history table for auditing.
  • Long multi-step workflows: use durable execution (Temporal) rather than hand-rolled state.
Metrics and monitoring systemDatadog/Prometheus · high write volume, time-series
  • Agents batch and push (or the server pulls) samples → Kafka → ingestion → time-series DB.
  • TSDB: compress with delta-of-delta timestamps and XOR floats (Gorilla); partition by series and time block.
  • Downsample older data (1 s → 1 min → 1 h) to control cost. Cardinality (tag combinations) is the main scaling risk.
  • Alerting evaluator runs rules on a schedule; dedupe and route alerts.
Part 5

AI-era systems

LLM-powered products reuse all the fundamentals above. The new constraints are GPU cost, token-based throughput, long and variable latency, and non-deterministic output that needs evaluation.

AI numbers to knowTokens, vectors, GPU memorynew
FactApprox value
1 token (English)≈ 4 chars ≈ 0.75 words
One page of text≈ 500–700 tokens
Embedding, 1,536 dims × float32≈ 6 KB per vector
10 M chunks of 1,536-dim vectors≈ 60 GB raw (+50–100% for HNSW graph)
Model weights at FP162 bytes/param: 8B ≈ 16 GB, 70B ≈ 140 GB
Datacenter GPU memory80–192 GB per GPU
Time to first token (target)< 0.5–1 s
Decode speed per stream~30–150 tokens/s

Cost scales with tokens, so estimates should be in tokens/s and $/1M tokens, not only QPS.

RAG system (chat over your documents)Ingestion, retrieval, generation, evaluationnewcore
Ingest: sources → parse (PDF, HTML) → chunk (300–800 tokens, overlap) → embed → vector index + keyword index + metadata (ACLs) Query: question → (rewrite) → hybrid search: BM25 + vector ANN → rerank (cross-encoder) → top-k chunks → prompt with citations → LLM → stream answer (SSE) → log for evals
  • Hybrid retrieval beats pure vector search for names, codes and exact terms. Fuse with reciprocal rank fusion.
  • ANN indexes: HNSW (graph, fast, memory-heavy), IVF (clusters), PQ (compressed vectors). Recall vs latency vs memory.
  • Permissions: filter by the user's ACLs at query time (pre-filter on metadata), never after generation.
  • Freshness: incremental re-indexing via CDC or document change events; re-embed when the embedding model changes (versioned indexes).
  • Evaluate retrieval (recall@k, MRR) and generation (faithfulness/groundedness, answer relevance) on a golden set, with LLM-as-judge plus human spot checks.
Key insightSay when RAG is the wrong tool: small static corpora fit in a long context window with prompt caching; structured questions are better served by text-to-SQL or tool calls.
LLM inference servingBatching, KV cache, GPU utilization, autoscalingnew
  • Two phases: prefill (process the prompt, compute-bound, sets time to first token) and decode (one token at a time, memory-bandwidth-bound).
  • Continuous batching: add and remove requests from the batch at every decode step to keep the GPU busy (vLLM, TGI, TensorRT-LLM).
  • KV cache stores attention state per request and grows with context length; it often limits concurrency. PagedAttention allocates it in blocks.
  • Prefix / prompt caching: reuse the KV cache for shared system prompts and documents. Big latency and cost win.
  • Other levers: quantization (FP8/INT4), speculative decoding (a small draft model proposes tokens), tensor/pipeline parallelism across GPUs, disaggregated prefill and decode pools.
  • Autoscale on queue depth and KV-cache utilization, not CPU. GPU cold start (loading weights) takes minutes, so keep warm capacity.
  • Route by request: small cheap model for easy queries, large model for hard ones.
LLM gateway / AI platform APIToken rate limits, cost tracking, fallbacks, streamingnew
  • One internal endpoint in front of many model providers: auth, per-team quotas, logging, PII redaction.
  • Rate limit on tokens per minute as well as requests; estimate input tokens up front, reconcile output tokens after.
  • Fallbacks across providers/regions on 429/5xx; circuit breakers; timeouts sized for long generations.
  • Streaming via SSE end to end; the gateway must not buffer the whole response.
  • Caching: exact-match cache, provider prompt caching, and semantic caching (embedding similarity) for FAQ-like traffic, with care about correctness.
  • Cost attribution per team, feature and model; budgets and alerts.
AI agent platformTool calling loops, sandboxes, durable state, MCPnew
  • Loop: model proposes a tool call → platform validates and executes → result appended to context → repeat until done or budget exhausted.
  • Agent runs last minutes to hours, so treat them as durable workflows (Temporal-style): checkpoint each step, resume after crashes, make tool calls idempotent.
  • Tools via a standard interface (Model Context Protocol, function schemas); per-tool auth scoped to the end user.
  • Code execution in isolated sandboxes (gVisor, Firecracker microVMs) with network and resource limits.
  • Guardrails: human approval for side effects (payments, emails), prompt-injection defenses for untrusted tool output, step and cost limits.
  • Context management: summarize or compact history, retrieve memory on demand, keep the window under budget.
  • Observability: trace every step (prompt, tool call, tokens, latency) for debugging and evals.
Recommendation systemCandidate generation, ranking, feature store
Millions of items → candidate generation (two-tower embeddings + ANN, co-visitation, trending) → ~1,000 → ranking model (features from feature store) → ~100 → re-rank (diversity, business rules) → ~20 shown
  • Feature store with an offline store (training) and an online store (low-latency serving); avoid training/serving skew.
  • Log impressions and interactions to Kafka for training data and real-time features.
  • Cold start: popularity and content-based features for new users and items.
  • Evaluate offline (NDCG, AUC) then online via A/B tests.
Evals, guardrails and moderationShipping non-deterministic systems safelynew
  • Offline eval suite (golden datasets, LLM-as-judge, regression tests on prompts) runs in CI before a prompt or model change ships.
  • Online: sample production traffic for grading, user feedback signals, A/B tests on quality and cost.
  • Input and output guardrails: classifiers for PII, toxicity and jailbreaks; schema validation for structured output.
  • Moderation pipeline for user content: fast cheap classifier on everything, escalate uncertain cases to an LLM, then to humans; queue with SLAs.
Part 7

Further reading

ResourcesBooks, courses, blogs
  • Designing Data-Intensive Applications (Kleppmann): the single best depth resource.
  • System Design Interview Vol. 1 & 2 (Alex Xu) and ByteByteGo: breadth of classic problems.
  • Hello Interview: modern problem walkthroughs.
  • AI Engineering and Designing Machine Learning Systems (Chip Huyen): for AI systems.
  • Engineering blogs: Netflix, Uber, Discord, Stripe, Cloudflare, AWS Builders' Library.