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).
Design process
Use the same six steps for every design, so nothing important gets skipped and every decision has a reason behind it.
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.
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."
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.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.
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.
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-off | Pick the left when… |
|---|---|
| Consistency vs availability | Money, inventory, auth. Wrong data costs more than downtime. |
| Latency vs throughput | User-facing request paths. Batch and buffer for background work. |
| Push vs pull | Few consumers, low fan-out, real-time needs. |
| Normalize vs denormalize | Write-heavy data with many relationships; denormalize for hot reads. |
| Sync vs async | The caller needs the result now. Otherwise put a queue in between. |
| Strong vs eventual | Read-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. |
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
| Fact | Value |
|---|---|
| Seconds per day | 86,400 ≈ 10⁵ |
| 1 M requests/day | ≈ 12 QPS |
| 100 M requests/day | ≈ 1,200 QPS |
| 1 B requests/day | ≈ 12,000 QPS |
| Peak vs average | 2–3× (up to 10× for events) |
| 2¹⁰ / 2²⁰ / 2³⁰ / 2⁴⁰ / 2⁵⁰ | KB / MB / GB / TB / PB |
| UUID / bigint / timestamp | 16 B / 8 B / 8 B |
| Short text post with metadata | ~1 KB |
| Compressed photo | 200 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?
| Component | Roughly handles |
|---|---|
| Stateless app server | 1K–10K RPS |
| Postgres / MySQL primary | 5K–20K simple writes/s, more reads; ~10 TB comfortable |
| Redis node | 100K+ ops/s, memory-bound (~100 GB) |
| Kafka broker | 100s of MB/s; cluster: millions msgs/s |
| Cassandra / DynamoDB | Scales linearly; ~10K ops/s per node |
| WebSocket server | 50K–1M idle connections (tuned) |
| NIC | 10–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
| SLA | Per year | Per month |
|---|---|---|
| 99% | 3.65 days | 7.3 h |
| 99.9% | 8.77 h | 43.8 min |
| 99.99% | 52.6 min | 4.38 min |
| 99.999% | 5.26 min | 26 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.
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
| Pattern | How | Use when |
|---|---|---|
| Cache-aside | App reads cache; on miss reads DB and fills cache | Default choice; tolerant of cache failure |
| Read-through | Cache library loads from DB on miss | Same as above, less app code |
| Write-through | Write cache and DB synchronously | Read-after-write must hit cache |
| Write-behind | Write cache, flush to DB asynchronously | Write-heavy counters; risk of loss |
| Write-around | Write DB only; cache fills on read | Data 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.
Choosing a databaseSQL vs NoSQL, B-tree vs LSMcore
| Type | Examples | Good at |
|---|---|---|
| Relational | Postgres, MySQL, Aurora, Spanner | Transactions, joins, constraints, flexible queries |
| Key-value | Redis, DynamoDB | Single-key lookups at huge scale |
| Wide-column | Cassandra, ScyllaDB, Bigtable | Write-heavy time-ordered data, partition + sort key |
| Document | MongoDB, Firestore | Nested, schema-flexible aggregates |
| Search | Elasticsearch, OpenSearch | Full text, faceting, relevance |
| Time-series | Prometheus, InfluxDB, TimescaleDB | Metrics, downsampling, retention |
| Graph | Neo4j | Multi-hop relationship queries |
| Vector | pgvector, Pinecone, Milvus | Nearest-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.
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_idfor user-scoped data,conversation_idfor 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.
| Model | Guarantee | Example need |
|---|---|---|
| Linearizable | Behaves like one copy; reads see the latest write | Locks, leader election, balances |
| Sequential | All see the same order, not necessarily real-time | Replicated logs |
| Causal | Cause before effect (reply after message) | Comments, chat |
| Read-your-writes | You see your own updates | Profile edits |
| Monotonic reads | Never go back in time | Feeds, timelines |
| Eventual | Converges if writes stop | Like 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".
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.
| Transport | Direction | Use for |
|---|---|---|
| Short polling | Client pulls | Rare updates, simplest |
| Long polling | Client pulls, server holds | Fallback where sockets are blocked |
| SSE | Server → client | Notifications, LLM token streaming |
| WebSocket | Bidirectional | Chat, collaboration, games |
| Webhooks | Server → server | Third-party callbacks |
Rate limiting algorithmsToken bucket and friends
| Algorithm | Behavior | Trade-off |
|---|---|---|
| Token bucket | Refill r tokens/s up to capacity b | Allows bursts; most common |
| Leaky bucket | Queue drains at a fixed rate | Smooth output, adds latency |
| Fixed window | Counter per minute | Cheap; 2× burst at window edges |
| Sliding log | Store every timestamp | Exact; memory-heavy |
| Sliding window counter | Weighted blend of current and previous window | Accurate 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).
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
- 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-RemainingandRetry-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.
LRU cacheIn-process and distributed · O(1) get/putcore
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
- 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).
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=2for 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
- 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
- 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 RedisSET NX EXwith 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.
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:
ZINCRBYto update,ZREVRANKfor a player's rank,ZREVRANGEfor 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_atindexed. Schedulers poll due jobs withSELECT … FOR UPDATE SKIP LOCKEDor 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.
No topics match that filter.
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
| Fact | Approx 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 FP16 | 2 bytes/param: 8B ≈ 16 GB, 70B ≈ 140 GB |
| Datacenter GPU memory | 80–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
- 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.
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
- 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.
Current trends
Patterns that are common in production today. Use them where they solve the problem in front of you, not for their own sake.
Durable executionTemporal, Restate, Step Functions
Write long-running, multi-step workflows as code; the engine persists each step and retries on failure. Replaces hand-built sagas, cron chains and state tables. Strong fit for payments, onboarding and AI agents.
CDC and event-driven architectureDebezium, outbox, streaming ETL
Stream database changes from the write-ahead log to Kafka, then feed caches, search indexes and warehouses from that stream. One source of truth, many derived views.
Postgres as the default for (almost) everythingpgvector, JSONB, queues, full text
For moderate scale, one Postgres can cover relational data, documents (JSONB), vectors (pgvector), queues (SKIP LOCKED) and simple search. Fewer moving parts. Distributed SQL (Spanner, CockroachDB, Aurora DSQL) when you outgrow a single primary.
Lakehouse and open table formatsIceberg, Delta, object storage
Analytics data as Parquet on S3 with an Iceberg/Delta catalog, queried by Spark, Trino, Snowflake or DuckDB. Separates storage from compute and avoids lock-in.
Cell-based architectureBlast-radius control at AWS scale
Split the system into identical, independent cells, each serving a subset of customers. A bad deploy or a poison request takes down one cell, not everyone. Pairs with gradual, cell-by-cell rollouts.
Edge compute and serverlessWorkers, Lambda, edge KV
Run auth, personalization and routing close to users. Serverless removes capacity planning for spiky loads; watch cold starts, connection limits to databases and cost at steady high load.
Local-first and CRDT syncOffline-capable apps
Data lives on the device and syncs in the background with CRDT merge. Instant UI and offline support; the server becomes a sync and backup peer. Seen in Figma, Linear and Notion-like apps.
Cost awareness (FinOps)Every design has a dominant cost line
Know the dominant cost line (egress, GPU hours, storage tiering, cross-AZ traffic) and one way to cut it. This matters most for AI systems.
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.