How to use: Click Hide answers to enter self-test mode — each question collapses, and you tap it to check yourself after answering aloud. Toggle back to read straight through, or Print for a paper copy. Anything you can’t reconstruct from memory is a weak spot to revisit.
System design is the process of defining a system’s architecture, components, data, and interactions to satisfy two kinds of requirements: functional (what the system must do — post a tweet, place an order) and non-functional (how well — latency, availability, consistency, durability, cost). There is never one perfect design, only trade-offs you can justify — which is exactly what an interviewer probes.
Use a repeatable framework so you never freeze: clarify requirements → estimate scale → define the API → design the data model → sketch the high-level architecture → deep-dive one or two hard components → identify bottlenecks and scale them.
Vertical scaling (scale up) means making one machine more powerful — more CPU, RAM, or disk. It’s simple and needs no code changes, but it hits a hard hardware ceiling and the single machine stays a single point of failure.
Horizontal scaling (scale out) means adding more machines behind a load balancer. It scales almost without limit and tolerates failures, but it requires services to be stateless (so any node can handle any request) and brings distributed-systems complexity. Databases scale out via replication (reads) and sharding (writes).
It’s back-of-the-envelope math done before designing, to size infrastructure and reveal the real bottleneck. You estimate traffic (daily actions → QPS, split reads vs writes; peak ≈ 2–3× average), storage (bytes × objects × retention), bandwidth (QPS × payload), and cache memory (the hot ~20%).
The key shortcut: a day ≈ 100,000 seconds, so any per-day number ÷ 100,000 gives per-second. Round aggressively and state assumptions out loud.
HTTP is an application-layer, stateless request/response protocol over TCP. Stateless means each request is self-contained — the server keeps no memory of prior requests unless the client sends state (a cookie or token). That’s what lets any server handle any request, enabling horizontal scaling.
HTTPS is HTTP over TLS. The protocol evolved 1.1 → 2 (multiplexing) → 3 (QUIC over UDP).
Four layers, each with one job, each wrapping the layer above: Application (HTTP/DNS) → Transport (TCP/UDP) → Internet (IP routing) → Link (physical/WiFi).
TCP is reliable, ordered, connection-based (3-way handshake SYN → SYN-ACK → ACK), retransmits lost packets — use for web, APIs, correctness. UDP is connectionless with no guarantees and minimal overhead — use for video calls, streaming, gaming, DNS, where speed beats a dropped packet.
This walks the whole stack: (1) browser/OS checks its cache for the IP; (2) DNS resolution — resolver → root → TLD → authoritative returns the IP; (3) TCP handshake; (4) TLS handshake negotiates keys and verifies the certificate; (5) the HTTP request goes out; (6) server-side, a load balancer picks an app server, which checks a cache and falls back to the database on a miss; (7) the response returns and the browser renders it (HTML → DOM → fetch CSS/JS/images → paint).
Relational databases store data in tables with a fixed schema, rows linked by keys, queried with SQL. Their strength is correctness through ACID:
Ideal for structured, related data needing strong guarantees. The weakness is scaling writes horizontally. Scale path: indexing → read replicas → caching → sharding.
An index is an auxiliary structure — usually a B+ tree — that maps a column’s values to row locations, turning an O(n) full-table scan into an O(log n) lookup. The trade-off: indexes make reads faster but writes slower (every write maintains every index) and use extra storage, so you index columns you filter, sort, or join on — not everything.
Types: primary/clustered (physical row order), secondary, composite (leftmost-prefix rule), and covering (contains all queried columns, never touches the table).
NoSQL databases have a flexible schema, scale out natively, and are often eventually consistent. Data is usually denormalized so a read hits one place. Four types:
Choose SQL for structured, related data with transactions and joins; choose NoSQL for huge scale, flexible schemas, and simple access patterns. Real systems use both (polyglot persistence).
A cache stores hot data in a faster/closer layer so most requests skip the slow source. It works because of locality of reference. Layers run browser → CDN → application cache (Redis/Memcached) → DB buffer.
The two hard problems are invalidation (knowing when data is stale) and stampede (many misses hammering the DB). Measure with hit ratio.
Thrashing happens when the working set (memory actively needed) exceeds physical RAM, so the OS spends most of its time swapping pages between RAM and slow disk instead of working. Throughput collapses — the machine looks busy but does almost nothing, and adding more concurrent work makes it worse.
Fix: reduce concurrency, add RAM, improve locality, or switch I/O-bound work to an async/event-loop model. It’s the same failure mode as a cache with too low a hit ratio.
A thread is the smallest unit of execution; multiple threads live in one process and share its heap, each with its own stack. Processes are isolated and heavier; threads are lightweight, but one bug can crash the whole process.
Concurrency is interleaving many tasks (possible on one core) — about structure. Parallelism is running many tasks at once on multiple cores — about execution. Shared memory brings race conditions and deadlock, addressed with locks/mutexes/atomics (overuse causes contention).
A load balancer sits in front of servers and distributes requests so none is overwhelmed — the enabler of horizontal scaling and HA. L4 routes by IP/port (fast); L7 routes by URL/headers/cookies (smart — path routing, TLS termination). Algorithms: round robin, weighted, least connections, least response time, IP hash. Health checks remove dead nodes. Since the LB is itself a possible SPOF, run it active-active/active-passive with a floating IP.
With naive hash(key) % N, changing N remaps almost every key → mass cache misses. Consistent hashing places keys and servers on a ring; a key belongs to the first server clockwise. Adding/removing a server remaps only one arc — about 1/N. Virtual nodes hash each server to many ring points, so load is even and a departing server’s keys spread across many neighbors.
Sharding splits one dataset horizontally across machines, each holding a different subset — scaling writes and storage. Replication copies the same data for read scaling and availability. Real systems do both. Strategies: range (hotspot risk), hash (even; pair with consistent hashing), directory (flexible, possible bottleneck), geo. The shard key needs high cardinality + even access or you get hotspots.
hash(user_id) spreads load evenly; sharding by country would overload one shard and idle the rest.A space-efficient probabilistic structure answering set membership as “definitely not” or “possibly yes.” No false negatives, some false positives, and you can’t delete from a basic one. It’s a bit array plus k hash functions. Its value is skipping an expensive lookup when the answer is a certain “no.”
Replication keeps copies of data for availability, read scaling, geo-latency, and durability. Models: single-leader (simple, leader is a bottleneck/SPOF), multi-leader (needs conflict resolution), leaderless (quorum-based). Sync = strong but slower/less available; async = fast but replication lag → stale reads. Quorum: with N replicas, W + R > N guarantees you read the latest write.
LSM-trees are optimized for write-heavy workloads. Write path: append to a WAL and an in-memory sorted memtable; when it fills, flush to an immutable sorted SSTable (fast sequential write); background compaction merges SSTables and drops old values. Reads use Bloom filters and sparse indexes to skip files. Versus a B-tree (in-place updates, better for reads/range scans), LSM trades read amplification for high write throughput.
Ordinary B-tree indexes handle one dimension, not 2D proximity, so geo systems map 2D space to something indexable where nearby points cluster. Techniques: Geohash (lat/lng → sortable string, neighbors share a prefix), Quadtree (split into four), R-tree (bounding boxes — PostGIS), and S2/H3 (sphere cells/hexagons — Google/Uber).
The rule is backward compatibility — old and new code run at once during a rolling deploy. Use the expand–contract pattern: (1) expand — add the new column/table as nullable; (2) migrate — dual-write to old and new, backfill history in throttled batches; (3) switch reads to the new field; (4) contract — drop the old field a release later. Watch big-ALTER locking (use online-DDL) and keep a rollback path.
name into first/last: add columns, dual-write, backfill in chunks, flip reads, then drop name later — both app versions work throughout.CAP: during a network partition, a system guarantees only Consistency or Availability, not both; with no partition you can have both. CP systems reject requests to stay correct (banking, booking); AP systems serve stale data to stay up (feeds, catalogs). PACELC extends it: on Partition choose A or C; Else choose Latency or Consistency — strong consistency costs latency even when nothing is broken.
A spectrum: linearizable/strong → sequential → causal → eventual. Cheaper client-centric guarantees are often enough: read-your-writes, monotonic reads, monotonic writes, writes-follow-reads. Tune with quorum settings. A common design is eventual by default, read-your-writes on the user’s own data.
Isolation (the I in ACID) controls how much concurrent transactions see of each other. Anomalies: dirty read (reading uncommitted data), non-repeatable read (same row, different values), phantom read (same query, new rows). Levels: Read Uncommitted → Read Committed → Repeatable Read → Serializable (each stops one more; less concurrency). Enforced by locking or MVCC.
A message queue is an asynchronous buffer: a producer drops a message and a consumer processes it later. It decouples services, absorbs spikes, and adds resilience. Delivery: at-most-once, at-least-once (may duplicate → consumers must be idempotent; the common choice), exactly-once (hard). A dead-letter queue parks failures. A plain queue (RabbitMQ/SQS) removes a message once consumed; a log (Kafka) retains it, letting consumers track an offset and replay.
In a queue, each message is consumed by exactly one consumer. In pub/sub, publishers send to a topic and every subscriber gets its own copy — one-to-many. Delivery is push or pull. The big win is extensibility: adding a subscriber needs no publisher change.
order.created event reaches the email, analytics, and inventory services independently; adding a loyalty service later touches nothing upstream.Services react to events rather than calling each other directly — loose coupling, scalability, resilience — at the cost of eventual consistency and harder debugging. Patterns: Pub/Sub, Event Sourcing (event log is the source of truth), CQRS (separate write and read models), and Saga (a multi-service transaction as local steps with compensating actions, avoiding 2PC).
Yes — a table with a status column, with workers claiming rows via SELECT … FOR UPDATE SKIP LOCKED. Its key use is the Transactional Outbox, which solves the dual-write problem: in one DB transaction you write the business row and an outbox event row, then a relay publishes outbox rows to the real broker. The event is published if and only if the data committed.
order_created outbox row commit together; even if the app crashes right after, the relay still publishes the event — no lost or phantom events.A single point of failure is any component whose failure takes down the system — a lone LB, DB primary, cache, broker, AZ, or DNS. Eliminate with redundancy: replication with failover, active-active, multi-AZ/region, health checks. Nines: 99.9% ≈ 8.7h down/yr, 99.99% ≈ 52 min. Components in series multiply availability down; redundant ones raise it. Habit: for every box, ask “what if this dies?”
A container packages an app with its dependencies and shares the host OS kernel, isolated by namespaces and cgroups. A VM runs a full guest OS on a hypervisor. Containers are MBs and start in seconds (weaker isolation); VMs are GBs and start in minutes (stronger isolation). Containers are the foundation for Kubernetes orchestration.
A service registry tracks live instances; services register on startup and deregister on shutdown, and clients look up addresses. Discovery is client-side, server-side, or DNS-based. Heartbeats keep it accurate — push pings or pull /health probes; miss N and the instance is removed from routing.
A chain reaction: one node fails, its load shifts to survivors, they overload and fail, and the system collapses — amplified by retry storms. Defenses: timeouts, a circuit breaker (fail fast after N failures), retries with backoff + jitter, bulkheads (isolate resources), rate limiting / load shedding, and graceful degradation.
Build on observability: Metrics (what), Logs (exactly what), Traces (where). Detection escalates from static thresholds to statistical to seasonal/ML. Watch the four golden signals — Latency, Traffic, Errors, Saturation — and track p50/p95/p99 rather than averages, which hide the painful tail.
Cap requests per client per window. Algorithms: token bucket (allows bursts, common), leaky bucket (smooths), fixed window (boundary spikes), sliding window (accurate, more memory). Distributed challenge: a shared Redis counter (accurate, adds latency) vs local + sync (fast, approximate). Usually enforced at the API gateway, keyed by API key/user/IP.
INCR + TTL so all gateways share the count and a client can’t bypass it across servers.A distributed cache spreads data across nodes for more capacity, throughput, and availability (Redis Cluster, Memcached). Keys are placed with consistent hashing so adding/removing a node reshuffles only ~1/N of keys, and hot keys can be replicated. Hard problems: invalidation/staleness, hot keys, stampede, eviction, and the write path.
A CDN is a network of edge servers near users that cache content, cutting latency, offloading the origin, and absorbing spikes/DDoS. A pull CDN fetches from the origin on the first miss and caches it; a push CDN pre-uploads content. Users route to the nearest edge via DNS/anycast, freshness via TTL + Cache-Control/ETag, invalidation often via versioned URLs (app.v7.js).
Write-through writes cache and DB synchronously — always fresh, slower writes. Write-back writes the cache now and the DB later — fast, high-throughput, but risks data loss if the cache dies before flushing. Write-around writes straight to the DB and skips the cache (which fills on a later read) — good for write-heavy, rarely-read data.
The eviction policy maximizes hit ratio. LRU evicts the least-recently-used (the default); LFU the least-frequently-used; FIFO the oldest-inserted; TTL expires by time. Smarter: SLRU/2Q resist scan pollution, ARC adapts between LRU and LFU. LRU runs in O(1) with a hashmap plus a doubly-linked list.
A monolith is one deployable app; microservices are many small, independently deployable services owning their data. Microservices give independent scaling/deployment, autonomy, and fault isolation — but distributed-systems complexity, no cross-service joins, and heavy ops (discovery, gateways, observability, CI/CD). The stance is monolith-first: extract services only when scale or team boundaries justify it — premature microservices are a leading cause of failure.
Incrementally, never big-bang. The Strangler Fig pattern puts a proxy in front and routes one capability at a time to a new service until the monolith is retired. Choose boundaries by bounded context, not layers. The hardest part is splitting the shared database (dual-write + backfill, events, outbox). Avoid nano-services and the distributed monolith (services too coupled to deploy independently).
A good API is a clear, versioned contract. REST is resource-oriented, stateless, cacheable (can over/under-fetch). GraphQL lets the client request exactly the fields it wants in one query (complex caching). gRPC is binary Protobuf over HTTP/2 — fast, streaming — ideal for internal calls, not browser-native. REST best practices: resource URLs, correct methods/codes, /v1/ versioning, cursor pagination, idempotency keys on POST. An API Gateway is the single front door for routing, rate limiting, auth, TLS, aggregation, and logging.
Don’t block the client — accept and return immediately, deliver later. Options: polling (202 + a status URL), long polling (hold the request open), webhooks (the server calls a registered URL — efficient, needs a public endpoint), and WebSockets/SSE (real-time push). Flow: POST /jobs → 202 + jobId → queue → worker → result via webhook or poll.
202 + jobId instantly, queues the job, and fires a webhook to the client’s callback URL when done — no thread blocked for minutes.OAuth 2.0 is delegated authorization — an app accesses a user’s resources without their password. Four roles: Resource Owner (user), Client (app), Authorization Server (issues tokens), Resource Server (holds data). Authorization Code flow: redirect → login + consent to scopes → auth code → exchange for access + refresh token → call the API; PKCE secures mobile/SPA. OAuth handles access; OIDC adds an identity layer (ID token) — “Log in with Google.”
Session-based auth stores state on the server (easy to revoke, but needs sticky sessions or a shared store). Token-based auth has the client hold a self-contained signed token the server just verifies — stateless, so it scales horizontally. A JWT is header.payload.signature; the payload is readable but not encrypted, so never put secrets in it. JWTs are hard to revoke before expiry — mitigate with short-lived access tokens + refresh tokens, a denylist, and key rotation.
AuthN is who you are; authZ is what you may do. Models: ACL (per-resource list — fine-grained, unwieldy), RBAC (roles — the default), ABAC (attribute/context rules), ReBAC (relationship-based, Google Zanzibar). Rule engines like OPA externalize policy from code.
Pull = client asks when it wants data (simple, laggy). Push = server sends as it happens (real-time, stateful). Feed fan-out on write (push) copies a post into all follower feeds (fast reads, celebrity write storm); fan-out on read (pull) builds the feed at open time (cheap writes, heavy reads). Real systems use a hybrid — push for normal users, pull for celebrities.
You can spend memory to reduce latency (caching, indexes, denormalization, materialized views, precomputation, fan-out-on-write) or save memory and pay in compute/latency (compute-on-demand, compression, Bloom filters, sampling). The choice depends on read/write ratio, data size, and cost.
Latency is the time for one request (p50/p95/p99); throughput is requests per second. Batching/buffering/queuing raises throughput but adds latency (items wait to batch). Little’s Law (L = λ × W) shows queues fill under load, so latency climbs even at max throughput. Optimize latency on user-facing paths, throughput on bulk pipelines.
From CAP: during a partition, pick CP (stay correct, reject requests) or AP (stay up, serve stale), and decide this per operation via quorum tuning. CP: payments, inventory, booking, auth. AP: feeds, likes, catalogs, analytics.
At scale, fast + approximate often beats slow + exact: sampling, HyperLogLog (approximate unique counts in tiny memory), Bloom filters, top-K, ANN. Fine for view/like counts, trending, dashboards, recommendations — not for bank balances, checkout inventory, or billing.
By access pattern and requirements. Pick SQL for structured, related data with ACID transactions, joins, and strong consistency (payments, orders). Pick NoSQL for massive scale, flexible schemas, high write throughput, and simple access (feeds, catalogs, sessions, time-series). Real systems use polyglot persistence — both, each for what it does best.
Say it out loud, in order: (1) Requirements — functional + non-functional; (2) Capacity estimation — DAU, read/write QPS, storage, bandwidth; (3) API design; (4) Data model — SQL vs NoSQL; (5) High-level design — client → LB → services → cache/DB/queue; (6) Deep-dive on one or two hard parts; (7) Bottlenecks & scaling — caching, sharding, replication, queues; (8) Trade-offs — stated explicitly. Every problem below is this framework plus one or two unique concepts.
Unique concept: the video pipeline + CDN. On upload, transcode into multiple bitrates for adaptive bitrate streaming (HLS/DASH), store in object storage, and distribute via CDN edges. Live adds low-latency chunked delivery. Deep-dive the encoding pipeline (an event-driven queue of transcode jobs) and CDN caching; the trade-off is storage (many bitrates) vs playback smoothness.
Unique concept: feed generation — fan-out on write vs read, resolved as a hybrid. Photos in object storage + CDN, metadata in a DB, feeds precomputed for most users and pulled for celebrities, sharded by user, hot feeds cached. Deep-dive the timeline fan-out and celebrity problem.
Unique concept: geospatial matching plus recommendations. Index locations with geohash/S2 for nearby profiles, and record swipes so a match is a mutual right-swipe. Deep-dive geo queries and de-duplicating seen profiles; the trade-off is recommendation quality vs latency.
Unique concept: real-time messaging over persistent WebSockets — presence, delivery/read receipts, a queue for offline users, push notifications, per-conversation ordering, group fan-out, and end-to-end encryption. Deep-dive delivery/receipts and group fan-out; scale with connection servers sharded by user.
Unique concept: the “For You” ranking pipeline plus a video CDN. An ML pipeline generates and ranks candidate videos per user, and the app prefetches/caches the next videos for instant playback. Deep-dive candidate generation → ranking and CDN prefetch; the trade-off is freshness vs precompute cost.
Unique concept: secure sandboxed execution at scale. A submission becomes a job on a queue, runs in an isolated container with strict CPU/memory/time limits against test cases, and returns a verdict. Deep-dive sandboxing untrusted code and autoscaling workers; the trade-off is isolation strength vs container startup speed.
Unique concept: ACID + idempotency + CP. Every transaction is atomic and exactly-once — idempotency keys prevent a double-charge on retry — favoring strong consistency over availability. Deep-dive idempotency, a double-entry ledger, and cross-bank coordination via a Saga or two-phase approach.
Unique concept: concurrency control to prevent double-booking under massive contention. Use DB row locking (SELECT … FOR UPDATE), transactions, or reserve-with-timeout so exactly one of thousands competing for the last seat wins. Firmly a CP choice — throughput sacrificed for correctness.
Unique concept: an event-driven media pipeline. A new title is chunked, transcoded in parallel into many device formats, validated, and published to the CDN. Deep-dive the pipeline orchestration via queues/events and the massive parallelism; the trade-off is storage/compute cost vs device coverage.
Unique concept: a three-sided marketplace with geo dispatch — customer, restaurant, driver. Match orders to nearby drivers with a geospatial index, track live locations over WebSockets, and estimate ETAs. Deep-dive dispatch and real-time tracking; the trade-off is match optimality vs speed.
Unique concept: catalog + cart + inventory + checkout consistency. Catalog in NoSQL + search index, cart in a fast store, orders/payments in SQL. Checkout and inventory are CP (never oversell) while the catalog is AP — consistency chosen per feature. Deep-dive inventory decrement under concurrency and search.
Unique concept: geo indexing + routing. Map tiles from a CDN, shortest-path routing (Dijkstra/A*, often with contraction hierarchies) over a road graph, real-time traffic overlaid. Deep-dive planet-scale routing and tiling; the trade-off is route accuracy vs compute, precompute vs real-time.
Unique concept: storage + search at scale with sharding. Emails sharded by user, a full-text inverted index for search, labels/threads for organization. Deep-dive search indexing and storage tiering; replicate for availability.
Unique concept: real-time turn-based state with matchmaking. WebSockets carry live moves, an authoritative server validates each move (anti-cheat), and matchmaking pairs players by Elo. Deep-dive game-state consistency and reconnection; the trade-off is latency vs validation.
Unique concept: geo dispatch + surge pricing. Drivers publish location into a geo index (H3/S2), riders match to the nearest driver, surge is computed per cell from supply/demand, and live tracking runs over WebSockets. Deep-dive matching, the geospatial index, and surge; scale by partitioning per region.
Unique concept: real-time concurrent editing with conflict resolution. Operational Transformation (OT) or CRDTs merge simultaneous edits so every client converges. WebSockets sync changes, presence/cursors show collaborators, version history is kept. Deep-dive OT/CRDT and convergence; the trade-off is the consistency model vs responsiveness (edits apply locally, reconcile async).