Tradeoffs Are the Grading Rubric
the sentence they're listening forJunior candidates argue about which option is correct. Senior candidates know there is rarely a correct option — there's a fit for the constraints in front of them. The interviewer is not checking whether you picked Redis or Memcached. They're checking whether you know what the pick costs.
So the unit of a good answer is not the decision. It's this sentence:
"I'd choose ┌─────────┐ because it gives us ┌─────────┐
│ X │ │ Z │
└─────────┘ └─────────┘
and I'm accepting ┌─────────┐ which is fine here because ┌──────────────┐
│ Y │ │ requirement │
└─────────┘ └──────────────┘
X = the choice Y = what it costs Z = what it buys
Say all three parts. "I'll denormalize the feed" is half an answer. "I'll denormalize the feed so reads are a single lookup, paying with write amplification and the risk of stale data, which is acceptable because this is a read-heavy timeline where a few seconds of staleness is invisible" is the whole answer. Same decision. One of them passes.
The Core Axes
the handful you'll trade on all dayAlmost every decision in a design lands on one of a few axes. Learn these and you'll recognize the tradeoff before you've finished hearing the question.
Wait for replicas to agree before answering (consistent, slower) or answer from the nearest copy (fast, maybe stale). You pay this on every replicated write, not just during failures.
Example: read from the primary for correctness, or from a replica to cut latency and offload the leader (Consistency Models).
Batch work together and you push more total volume, but each item waits for the batch to fill. Process items one at a time and each is fast, but the system does less overall.
Example: group writes into one commit, or buffer log lines before flushing — more throughput, higher tail latency.
When nodes can't talk, either refuse the request (consistent, unavailable) or serve what you have (available, possibly wrong). There is no third door once the network splits.
Example: a balance check should refuse; a like counter should serve stale (CAP).
More replicas, more regions, more backups mean fewer ways to lose data — and a bigger bill. The right number of nines is the one the data is worth, not the most you can buy.
Example: three-way replication for the system of record, single-copy cache that you can rebuild.
A rigid design is easy to reason about and hard to extend. A flexible one absorbs future needs and hides more moving parts. Generality you don't need yet is complexity you pay for now.
Example: a fixed schema vs a generic key-value blob; a direct call vs a plugin system.
Build it and you get exactly what you want plus the burden of running it forever. Buy or adopt and you get it now, bounded by someone else's roadmap and priced by their vendor.
Example: a managed queue vs your own, hosted search vs a self-run Elasticsearch cluster.
Read vs Write Optimization
the canonical tradeoff, worked throughThe most reused tradeoff in system design: you can make reads cheap or writes cheap, rarely both. Every technique here moves work from one side to the other. Which side you optimize follows from the read/write ratio, and you should have that ratio from your estimation (Estimation).
Denormalization, materialized views, and precomputed indexes all say the same thing — do the work once at write time so every read is a lookup. The clearest instance is feed fan-out.
FAN-OUT ON WRITE FAN-OUT ON READ
(precompute — cheap reads) (compute on demand — cheap writes)
Alice posts ──▶ copy into the feed Alice posts ──▶ append to Alice's
of every follower own list. Done.
(1 write → N writes)
Bob opens feed ──▶ gather posts from
Bob opens feed ──▶ read his list. everyone Bob follows,
One lookup. merge, sort.
(1 read → N reads)
Great when: reads ≫ writes, Great when: writes ≫ reads, or the
followers bounded. author has millions of followers.
Dies on: celebrities (one post Dies on: users following thousands
fans out to millions). (every open is a huge scatter-gather).
Neither wins outright, which is why real feeds go hybrid: fan-out on write for normal accounts, fan-out on read for celebrities, stitched together at read time. That hybrid is itself a tradeoff — more moving parts in exchange for handling both ends of the follower distribution (News Feed).
| Technique | Pays for | Costs you |
|---|---|---|
| Denormalization | Reads with no joins | Duplicated data, update fan-out, drift risk |
| Materialized view | Precomputed query results | Refresh lag, storage, staleness |
| Secondary index | Fast lookups on non-key fields | Slower writes, more space (Indexing) |
| Caching | Repeated reads served from memory | Invalidation, staleness, a cold-start cliff (Caching) |
| Fan-out on write | Feed reads as one lookup | Write amplification, celebrity blowup |
The Complexity Budget
every box has to earn its placeEach component you add is not free even if it's fast. It's another thing to deploy, monitor, secure, keep consistent, and page someone about at 3am. Treat complexity as a budget you spend, and make every box justify itself against a requirement you actually have.
The failure mode in interviews is the opposite: reaching for Kafka, a cache tier, and five microservices before anyone has established there's a problem that needs them. That reads as pattern-matching, not engineering.
For each component, ask:
Requirement it serves? ──no──▶ delete it. It's decoration.
│ yes
▼
Does the simple version fail? ──no──▶ keep the simple version.
│ yes (single DB, sync call, no cache)
▼
Fails on which number? ──▶ name it: QPS, data size, latency target,
│ blast radius. That number is your justification.
▼
Add the component. Now you can defend it.
The discipline is to start simple and let a specific number force each addition. One database until the read QPS exceeds what it serves — then a cache. One region until latency to distant users hurts — then a second (Scaling Patterns). Adding the machinery before the number arrives is spending budget you didn't need to.
Quick Reference
you choose / you pay with| You choose | You gain | You pay with |
|---|---|---|
| Strong consistency | Correct reads, single-copy illusion | Latency, availability under partition |
| Eventual consistency | Low latency, stays up in a partition | Stale reads, reconciliation logic |
| Denormalization | Read with no joins | Duplicated data, update fan-out |
| Normalization | One source of truth, clean writes | Joins on the read path |
| Caching | Fast repeated reads | Invalidation, staleness, cold starts |
| Fan-out on write | Cheap feed reads | Write amplification, celebrity blowup |
| Fan-out on read | Cheap writes | Expensive scatter-gather reads |
| Batching | Throughput | Higher tail latency |
| Synchronous call | Simple, immediate result | Tight coupling, blocking on failure |
| Async / queue | Decoupling, spike absorption | Eventual consistency, harder debugging |
| More replicas / regions | Durability, locality, failover | Cost, cross-region latency, conflicts |
| Sharding | Horizontal scale past one node | Cross-shard queries, rebalancing, hot keys |
| SQL | Transactions, joins, flexible queries | Harder horizontal scaling |
| NoSQL | Scale, schema flexibility | Query-first modeling, weaker transactions |
| Microservices | Independent deploy and scale | Network calls, distributed failure, ops load |
| Monolith | Simple deploy, local calls, real transactions | Scales as one unit, coupled teams |
| Build | Exactly what you need | You run it forever |
| Buy / managed | It works now | Vendor limits, cost, less control |