Component Drills
A rapid-fire drill on every building block: “When would you use it? When would you not? What’s the
alternative?” If you can answer these instantly for each component, you’ll never freeze reaching for the
right tool mid-design. Treat this as flashcards — cover the answer, respond aloud, check.
Prerequisites: Building Blocks, Deep Dives
Time to work through: ~25 minutes, then revisit as flashcards
How to use this
🚨 The skill: in a design, you must instantly know which component solves a problem and why it, not
the alternative. These drills build that reflex. For each: read the prompt, answer out loud (use →
because → not-the-alternative), then check. Redo the ones you miss.
The format for a strong answer: “Use X when [condition], because [benefit]; the alternative is Y, which
[trade-off].”
Load balancing & traffic
When do you add a load balancer, and L4 vs L7?
Add one the moment you have more than one server (which you always will) — it distributes traffic and
enables horizontal scaling and failover. **L4** (transport) routes by IP/port, is fast and protocol-agnostic;
**L7** (application) routes by HTTP content (path, headers, cookies) enabling smart routing, SSL termination,
and stickiness at some CPU cost. Use L7 for HTTP APIs needing content-based routing; L4 for raw throughput or
non-HTTP. [Load Balancers](/system-design/02-building-blocks/01-load-balancers.html)
API gateway vs load balancer — what's the difference?
A load balancer just distributes traffic across servers. An **API gateway** is an application-aware front
door that also does auth, rate limiting, request routing to microservices, aggregation, and protocol
translation. Use a gateway when you have many services and want to centralize cross-cutting concerns; it
often sits behind a load balancer. Don't reach for a gateway for a single simple service — it's overhead.
[API Gateway](/system-design/02-building-blocks/03-api-gateway.html)
When do you need a CDN?
When you serve static or cacheable content (images, video, JS/CSS, immutable blobs) to geographically
distributed users, or when origin bandwidth/latency is a bottleneck. It caches at edge locations near users
→ low latency and massive origin offload. Don't bother for purely dynamic, per-user, uncacheable responses to
a local audience. Mandatory for media at scale. [CDN](/system-design/02-building-blocks/04-cdn.html)
Caching
When do you add a cache, and what's the risk?
Add a cache for read-heavy workloads where the same data is read repeatedly and slight staleness is
acceptable — it slashes read latency and DB load. The risks: **cache invalidation** (stale data), added
complexity, and a stampede if a hot key expires. Don't cache write-heavy or strongly-consistent data
casually. Compute the read/write ratio first — high ratio → cache is the biggest cheap win.
[Caching](/system-design/02-building-blocks/05-caching.html)
Cache-aside vs write-through vs write-back?
**Cache-aside** (app reads cache, on miss loads DB and populates): simple, common, but first read misses and
staleness needs TTL/invalidation. **Write-through** (write cache+DB together): consistent reads, slower
writes. **Write-back** (write cache, async to DB): fast writes, risk of loss on crash. Default to cache-aside
+ TTL; use write-through when you can't tolerate staleness; write-back only when write latency dominates and
you accept loss risk. [Caching](/system-design/02-building-blocks/05-caching.html)
Databases
SQL vs NoSQL — how do you actually choose?
Choose **SQL** when you need ACID transactions, complex queries/joins, and strong consistency on structured,
related data (money, orders, users) — and the data fits a defined schema. Choose **NoSQL** when you need
massive horizontal scale, flexible/evolving schema, and simple access patterns (key lookups, appends), and
can accept eventual consistency — or when the data model is document/wide-column/graph-shaped. 🚨 The real
driver is the *access pattern* and *consistency need*, not "NoSQL scales better." [Choosing a Database](/system-design/03-data-and-storage/06-choosing-a-database.html)
When do you add a database index, and what's the cost?
Add an index when you frequently query/filter/sort by a column and the table is large enough that scans hurt
— it turns O(N) scans into O(log N) lookups. The cost: indexes consume storage and **slow down writes**
(every insert/update maintains the index), so don't index everything or columns you rarely query. Index the
read path's hot columns. [Indexing](/system-design/02-building-blocks/07-indexing.html)
Replication vs sharding — what does each solve?
**Replication** (copies of the data on multiple nodes) solves read scaling, availability, and durability —
reads spread across replicas, and a replica takes over on failure; but it doesn't help write scaling (all
writes still hit the primary) and adds consistency lag. **Sharding** (partitioning data across nodes) solves
write scaling and data-size limits — each shard handles a subset; but it adds routing complexity and makes
cross-shard queries/transactions hard. Reads → replicate; writes/size → shard; usually both.
[Replication](/system-design/02-building-blocks/08-replication.html) · [Sharding](/system-design/02-building-blocks/09-sharding.html)
B-tree vs LSM-tree storage engine?
**B-trees** (most SQL DBs) are read-optimized and update in place — great for read-heavy, mixed workloads and
range queries. **LSM-trees** (Cassandra, RocksDB) buffer writes in memory and flush sorted files, compacting
later — write-optimized, ideal for high write throughput (metrics, logs, KV stores) at some read/compaction
cost. Write-heavy → LSM; balanced/read-heavy → B-tree. [Storage Engines](/system-design/03-data-and-storage/01-storage-engines.html)
Messaging & async
When do you introduce a message queue?
When you need to decouple producers from consumers, absorb bursts, smooth load, do work asynchronously, or
buffer between systems of different speeds. It makes the producer's request fast (fire-and-forget) and lets
consumers process at their own pace with retries. Don't add one for simple synchronous request/response where
the caller needs an immediate result. [Message Queues](/system-design/02-building-blocks/10-message-queues.html)
Kafka (log) vs a traditional queue (SQS/RabbitMQ)?
A **log** (Kafka) is an append-only, retained, replayable, partitioned stream — many consumer groups read
independently, messages persist, great for event streaming, high throughput, and replay. A **traditional
queue** deletes on consumption, is simpler for task distribution to workers, and supports per-message
ack/visibility. Streaming/replay/multi-consumer → log; simple work distribution → queue. [Kafka](/system-design/02-building-blocks/11-kafka.html)
Scaling data structures
When do you use consistent hashing?
When you distribute keys across a *changing* set of nodes (a cache cluster, a sharded store) and want
adding/removing a node to move only ~1/N of keys, not remap everything. Plain `hash % N` remaps almost all
keys on a membership change — catastrophic for a cache (mass misses). Use consistent hashing + virtual nodes
for elastic clusters. [Consistent Hashing](/system-design/02-building-blocks/14-consistent-hashing.html)
When do you use a Bloom filter?
When you need a fast, memory-cheap "is this element *possibly* in the set?" check over a huge set, and can
tolerate false positives (never false negatives). Uses: "have I seen this URL?" (crawler), "might this key
exist before I hit disk?" (LSM read optimization), dedup. Not when you need exact membership.
[Probabilistic Data Structures](/system-design/02-building-blocks/17-probabilistic-data-structures.html)
When do you need a geospatial index?
When you answer "what's near this location?" over many moving/static points (nearby drivers, restaurants,
places) — a geohash/quadtree/S2 index makes proximity a cheap cell lookup instead of an O(N) distance scan.
Essential for ride-hailing, delivery, maps. [Geospatial Indexing](/system-design/02-building-blocks/21-geospatial-indexing.html)
How do you generate unique IDs at scale?
Options: **UUIDs** (random, no coordination, but big and unordered), **database auto-increment** (ordered but
a single bottleneck), **Snowflake** (timestamp + machine + sequence → sortable, distributed, compact — the
common choice), or **range allocation** (each server grabs a block). Need sortable + distributed + no
bottleneck → Snowflake-style. [Unique ID Generation](/system-design/02-building-blocks/16-unique-id-generation.html)
Real-time
WebSockets vs SSE vs polling vs long-polling?
**Polling** (client asks repeatedly): simple, wasteful, laggy. **Long-polling** (server holds request until
data): better, still HTTP overhead. **SSE** (server→client stream over HTTP): great for one-way pushes
(notifications, feeds). **WebSockets** (full-duplex persistent): for bidirectional real-time (chat, games,
collaboration). Bidirectional/low-latency → WebSocket; one-way server push → SSE; nothing real-time → don't
add complexity. [Real-Time Communication](/system-design/02-building-blocks/20-realtime-communication.html)
Rapid-fire “which component?” round
Cover the answers; name the component + why for each:
"Serve the same product image to users worldwide, fast."
CDN — edge caching near users; immutable image caches perfectly. [CDN](/system-design/02-building-blocks/04-cdn.html)
"Read the same DB rows millions of times/sec."
Cache (Redis) in front of the DB — read-heavy, high hit rate. [Caching](/system-design/02-building-blocks/05-caching.html)
"Send an email without making the user wait."
Message queue — async, decouple, retry. [Message Queues](/system-design/02-building-blocks/10-message-queues.html)
"Store 100 TB of user-uploaded videos cheaply."
Object storage (S3) + CDN — cheap, durable, scalable blobs. [Object Storage](/system-design/02-building-blocks/13-object-storage.html)
"Find the 20 nearest drivers to a rider."
Geospatial index (geohash/S2) in memory. [Geospatial](/system-design/02-building-blocks/21-geospatial-indexing.html)
"Full-text search over millions of documents."
Inverted index / search engine (Elasticsearch). [Search Systems](/system-design/02-building-blocks/12-search-systems.html)
"Handle 200K writes/sec that one DB can't take."
Shard the database (+ maybe LSM store, batching). [Sharding](/system-design/02-building-blocks/09-sharding.html)
"Distribute keys across a cache cluster that grows and shrinks."
Consistent hashing + virtual nodes. [Consistent Hashing](/system-design/02-building-blocks/14-consistent-hashing.html)
🛠️ Try it
1. Flashcard the components. Turn each <details> above into a flashcard (prompt front, →/because/
not-the-alternative back). Run through the deck daily until every answer is instant.
2. Reverse drill. Given a component, name three problems it solves and one it doesn’t. E.g. “message
queue → decoupling, burst absorption, async; not → synchronous request/response.”
3. Justify the rejection. For each design decision you make in a mock, force yourself to name the
alternative you rejected and why. The rejection reasoning is the signal interviewers grade.
Further reading