Foundations
Part 1System Design Fundamentals
HLD vs LLD. High-level design is the what: components, how they interact and why. Low-level design is the how: schemas, API contracts, algorithms, error handling. Interviews start high-level, then deep-dive one or two areas.
The four-step framework. Clarify requirements (3–5 min), estimate scale (5–7), draw the high-level design (15–20), then deep dive (10–15): schema, API, critical algorithms, edge cases and failures.
Functional vs non-functional. Functional requirements say what the system does; non-functional ones say how well: uptime, latency, throughput, consistency. The non-functional ones drive architecture, technology and cost.
Sync vs async. Sync is simple with immediate feedback, but the client blocks and services couple. Async through a queue decouples, absorbs spikes and allows retries, at the cost of eventual consistency and harder debugging.
API styles. REST for CRUD and public APIs, GraphQL when clients ask for exactly the data they need, gRPC (binary Protocol Buffers) for fast internal calls, WebSocket for server push.
| Synchronous | Asynchronous | |
|---|---|---|
| Feedback | Immediate | Later (ACK on receipt) |
| Coupling | Tight | Loose |
| Traffic spike | Client waits | Queue buffers it |
| Use for | Login, checkout, balance check | Video processing, emails, order fan-out |
- Designing before clarifying scope, scale and the read/write ratio.
- Skipping non-functional requirements: they decide the architecture.
- Diving into low-level detail before the high-level picture is agreed.
Part 2Infrastructure Building Blocks
CDN. Edge servers (PoPs) serve content near users: ~5–10 ms from a Mumbai PoP versus ~150–200 ms to a US origin. Cache static assets and identical-for-everyone responses; never user-specific data.
Versioned URLs. Put a content hash in the filename (app.a1b2c3d4.js) and cache it for a year as immutable. A deploy changes the URL, so users get fresh files with zero purge delay.
no-cache doesn't mean "don't cache". no-cache caches but revalidates (ETag, 304 Not Modified); no-store never caches; private keeps it out of the CDN; s-maxage gives the CDN its own TTL.
L4 vs L7 load balancing. L4 sees IP and port: very fast (<100 μs added), any protocol. L7 sees the whole HTTP request: routes by path, host, header or cookie and terminates TLS, adding 1–5 ms.
Shallow vs deep health checks. Point the load balancer at a shallow liveness check. A deep check that queries the database marks every server unhealthy when the DB is down; use deep checks for monitoring.
Stateless servers scale. Keep no session in server memory: sessions in Redis, data in PostgreSQL, files in S3. Then any server takes any request and a crash loses nothing.
| Monolith | Microservices | |
|---|---|---|
| Scaling | Everything together | Per service |
| Failures | One affects all | Isolated |
| Transactions | ACID is easy | Distributed, hard |
| Start here when | Small team, new product | Clear boundaries, strong DevOps |
- Caching user-specific or authenticated responses at the CDN.
- A deep health check behind the load balancer: one database outage removes every server.
- Microservices for a small team with unclear requirements; start with a monolith.
Part 3Back-of-the-Envelope Estimation
Per day to per second. A day is 86,400 seconds; round to 100,000. So X per day ÷ 100,000 ≈ X per second: 1 billion a day is about 10,000 a second.
The four estimates. Traffic (RPS, read/write split, peak), storage (objects × size × retention × replication), bandwidth (RPS × payload, ingress and egress) and infrastructure (servers, replicas, cache).
Latency ratios matter. L1 cache 0.5 ns, memory 100 ns, SSD random read 150 μs, HDD seek 10 ms; datacenter round trip 0.5 ms, cross-continent 150 ms. Cache aggressively, batch, and minimize round trips.
Media dominates storage. For a Twitter-like service, text is 30 GB a day but images and videos add ~7 TB a day: media is 99%+ of storage. Egress for images is why the CDN is essential.
Formulas to say out loud. RPS = daily actions ÷ 100,000. API servers = peak RPS ÷ 5,000 ÷ 0.7. Read replicas = read RPS ÷ 30,000. Cache = hot data: 20% of the total, or the last N days.
| Do | Don't | |
|---|---|---|
| Numbers | "~12,000, call it 10K" | "12,453 per second" |
| Traffic | Design for peak, 2–3x | Design for the average |
| Time | Approximate, move on | Spend over 5 minutes |
| Result | Tie numbers to decisions | Skip the "so what" |
- Forgetting growth: 100 GB now can be 1 TB in a year at 10x growth.
- Forgetting replication (500 GB × 3 = 1.5 TB) or that images make posts 100x bigger.
- Mixing megabytes and megabits: MB/s × 8 = Mbps.
Part 4Networking Fundamentals for System Design
TCP guarantees, at a price. TCP gives reliable, ordered delivery with flow and congestion control, but the handshake costs 1 RTT before the first request and one lost packet blocks everything behind it.
UDP when late data is useless. No handshake, an 8-byte header, message boundaries kept, no reliability. Use it for real-time video, voice, gaming, DNS and as QUIC's base.
HTTP/1.1 → 2 → 3. HTTP/1.1 sends one request at a time per connection. HTTP/2 multiplexes streams and compresses headers over one TCP connection. HTTP/3 runs QUIC over UDP: a lost packet only blocks its own stream.
Reuse connections. Each new connection pays TCP and TLS handshakes and starts in slow start. Keep-alive and connection pools avoid that; short connections also pile up in TIME_WAIT and exhaust ephemeral ports.
Scaling WebSockets. Connections are stateful, so the load balancer needs stickiness, and users on different servers reach each other through a pub/sub backbone (Redis, Kafka). Use SSE for one-way server push.
| gRPC | REST | |
|---|---|---|
| Format | Protobuf, binary | JSON, text |
| Browser support | Limited (grpc-web) | Native |
| Streaming | Full | Limited |
| Use for | Internal microservices | Public APIs, browsers |
- Opening a new connection per request instead of pooling: handshakes, slow start, port exhaustion.
- WebSocket for request-response or one-way push, where HTTP or SSE is simpler.
- Assuming HTTP/2 solved head-of-line blocking: it still rides one TCP connection.
Part 5Operating System Concepts for System Design
Processes isolate, threads share. A process owns its memory and file descriptors, so one crash kills only itself. Threads share the heap and descriptors with their own stacks: cheap to create and switch, but one crash kills them all.
Event loops scale connections. Thread-per-connection doesn't scale past ~10K connections. epoll registers descriptors once and returns only the ready ones, so one thread serves thousands (Node.js, nginx, Redis).
Context switches cost more than they look. Saving registers is cheap; the TLB flush, cold CPU caches and pipeline flush are not. Use async I/O, thread pools, batching and CPU affinity to switch less.
Write-ahead logging. Append the change to a sequential log and fsync it before touching data files; on a crash, replay the log. Sequential HDD writes run ~100 MB/s versus ~1 MB/s random.
Containers are namespaces plus cgroups. Namespaces isolate what a process sees (PID, network, mounts, users); cgroups limit what it uses (CPU quota, memory). Exceed the memory limit and the container is OOM killed.
| Mutex | Semaphore | RWLock | |
|---|---|---|---|
| Lets in | One thread | N threads | Many readers or one writer |
| Ownership | Locker unlocks | None | Per lock mode |
| Use for | Shared data | Pools, rate limits | Read-heavy data |
- Thread-per-connection for a million connections instead of an event loop.
- Taking locks in different orders across threads: a deadlock waiting to happen.
- Running a database that caches pages itself without O_DIRECT, so data is cached twice.
Part 6System Design Terminology & Theory
CAP is C or A during a partition. Partitions will happen, so P isn't optional. During one, a CP system refuses what it can't confirm (ZooKeeper, etcd); an AP system answers with possibly stale data (Cassandra, DNS).
PACELC. Even with no partition, strong consistency costs latency: synchronous replication waits for the slowest replica. DynamoDB and Cassandra are PA/EL; HBase is PC/EC.
Isolation levels. Read uncommitted allows dirty reads; read committed (PostgreSQL's default) allows non-repeatable reads; repeatable read (MySQL's default) allows phantoms; serializable allows none.
Consistency models. Linearizability respects real-time order; sequential keeps only each process's program order; causal keeps cause before effect (vector clocks); eventual only promises convergence.
Delivery semantics. At-most-once may lose messages; at-least-once may duplicate them, so consumers must be idempotent; exactly-once needs idempotent producers and consumers, or transactional messaging.
Error budgets. A 99.9% SLO allows 43.2 minutes of downtime in a 30-day month. Budget left: ship fast. Budget gone: freeze features and fix reliability. Keep the SLA looser than the SLO.
| ACID | BASE | |
|---|---|---|
| Consistency | Strong | Eventual |
| Approach | Pessimistic: lock early | Optimistic: resolve later |
| Scaling | Mainly vertical | Horizontal |
| Use for | Payments, inventory, accounts | Feeds, catalogs, sessions |
- "Pick 2 of 3" in CAP: partition tolerance is a given, not a choice.
- Assuming exactly-once delivery is free instead of making consumers idempotent.
- Saga compensations that aren't idempotent: they may run more than once.