system-design

Trade-off Drills

Naming trade-offs in clear language is the skill that separates strong candidates. This drill gives you the most common design decisions and trains you to articulate the trade-off both ways — because every real decision is a trade-off, and “it depends… on what” is the mark of maturity. Answer each aloud in trade-off vocabulary, then check.

Prerequisites: Trade-off Vocabulary, CAP & PACELC, Consistency Models Time to work through: ~25 minutes, then revisit


The skill

🚨 There are no right answers in system design, only trade-offs — and the interview grades whether you can name them. A weak candidate says “I’ll use NoSQL.” A strong one says “I’ll use NoSQL because the access pattern is key-value and I need horizontal write scaling, accepting eventual consistency and losing joins — if we needed transactions I’d switch to SQL.” Same choice, vastly different signal.

The template: “[Option A] gives [benefit] at the cost of [drawback]; [Option B] is the reverse. I’d pick [X] here because [which side the requirements favor].”

Every drill below: articulate both sides and what tips the decision. Cover the answer first.


The foundational trade-offs

Strong vs eventual consistency **Strong** guarantees every read sees the latest write — correct and intuitive, but costs latency and availability (must coordinate, and per CAP sacrifices availability under partition). **Eventual** lets reads see stale data briefly — fast, highly available, partition-tolerant, but the app must tolerate staleness and conflicts. Pick **strong** for money, inventory, anything where stale = wrong (payments, stock). Pick **eventual** for feeds, likes, view counts, caches where a second of staleness is harmless. The decision is: *what does incorrectness cost here?* [Consistency Models](/system-design/01-foundations/13-consistency-models.html)
CAP: consistency vs availability under a partition When the network partitions, you must choose: **CP** (refuse requests to stay consistent) or **AP** (stay available, serve possibly-stale data). CP for systems where wrong data is unacceptable (payments, config, a lock service); AP for systems where being down is worse than being slightly stale (feeds, shopping browse, DNS). And per **PACELC**, even without a partition, you trade latency vs consistency. Name which your system is and why. [CAP & PACELC](/system-design/01-foundations/14-cap-and-pacelc.html)
Latency vs throughput **Latency** is per-request time; **throughput** is requests/sec. They can conflict: batching improves throughput (amortize overhead) but adds latency (wait to fill a batch); tiny requests minimize latency but waste throughput. Optimize latency for user-facing critical paths; throughput for background/bulk pipelines. Batching, buffering, and pipelining trade one for the other. Know which your workload needs.

Data & storage decisions

SQL vs NoSQL **SQL:** ACID, joins, complex queries, strong consistency, defined schema — at the cost of harder horizontal scaling. **NoSQL:** horizontal scale, flexible schema, simple fast access patterns — at the cost of (usually) eventual consistency, no joins, and modeling around specific queries. Tips the decision: transactions + relational queries → SQL; massive scale + simple access + schema flexibility → NoSQL. Never "NoSQL because scale" without naming the access pattern. [Choosing a Database](/system-design/03-data-and-storage/06-choosing-a-database.html)
Normalization vs denormalization **Normalized:** no data duplication, easy consistent writes, but reads need joins (slower). **Denormalized:** data duplicated/precomputed for fast reads (no joins), but writes must update multiple copies and risk inconsistency. Normalize for write-heavy/consistency-critical relational data; denormalize for read-heavy systems where read latency matters (feeds, analytics). It's the read-vs-write-cost trade.
Replication lag: sync vs async replication **Synchronous** replication (wait for replicas before acking) → no data loss on primary failure, but slower writes and reduced availability if a replica is down. **Asynchronous** (ack immediately, replicate after) → fast writes, high availability, but risk losing recent writes on failure and replicas serve stale reads. Sync when you can't lose a write (some financial); async for most systems, accepting the read-your-writes gap. [Replication](/system-design/02-building-blocks/08-replication.html)

Architecture decisions

Monolith vs microservices **Monolith:** simple to build/deploy/debug, fast for small teams, no network overhead — but hard to scale teams and components independently, and one big deploy. **Microservices:** independent scaling/deployment, team autonomy, fault isolation — at the cost of huge operational complexity, network latency, distributed- system problems, and data consistency across services. 🚨 Start monolith; split to microservices when team/ scale pain justifies the complexity. Premature microservices is a classic anti-pattern. [Monolith vs Microservices](/system-design/05-architecture-patterns/01-monolith-vs-microservices.html)
Synchronous vs asynchronous communication **Sync** (request/response, caller waits): simple, immediate result, easy to reason about — but couples services (caller blocked, cascading failures) and limits throughput. **Async** (messages/events, fire-and- forget): decoupled, resilient, absorbs bursts, scales — but adds complexity, eventual consistency, and harder debugging/ordering. Sync when the caller needs the answer now and the callee is fast/reliable; async for slow work, decoupling, and burst absorption. [Event-Driven Architecture](/system-design/05-architecture-patterns/03-event-driven-architecture.html)
Fan-out on write vs fan-out on read **On write** (push to all followers' timelines when you post): fast reads (precomputed), but expensive writes for high-fan-out users (the celebrity problem) and wasted work for inactive followers. **On read** (gather at read time): cheap writes, but expensive reads. Tips it: read-heavy → write; but hybrid (write for normal users, read for celebrities) resolves the extreme. [Design Twitter](/system-design/12-case-studies/08-twitter.html)

Delivery & processing decisions

At-most-once vs at-least-once vs exactly-once delivery **At-most-once:** never duplicates, may lose messages — fast, for tolerable-loss data. **At-least-once:** never loses, may duplicate — the common default, paired with idempotency to neutralize duplicates. **Exactly-once:** ideal but impossible to truly guarantee end-to-end; approximated via at-least-once + idempotency. Pick at-least-once + idempotent consumers for almost everything; at-most-once only when loss is fine and duplicates are worse. [Idempotency](/system-design/04-distributed-systems/11-idempotency.html)
Batch vs stream processing **Batch:** process large volumes periodically — high throughput, exact, simple, but high latency (results are delayed). **Stream:** process events as they arrive — low latency, fresh, but harder (state, late/out-of- order events) and often approximate. Batch for exact/historical (billing, reports); stream for real-time (dashboards, alerts, fraud). Often both (Lambda/Kappa). [Batch vs Stream](/system-design/03-data-and-storage/10-batch-vs-stream.html)
Push vs pull **Push:** server sends immediately → low latency, but can overwhelm slow consumers and needs connection management. **Pull:** consumer fetches at its pace → natural backpressure, batching, slow-consumer-safe, but adds polling latency/overhead. Push for real-time delivery (notifications, kill switches); pull for consumer- paced processing (queue consumers, metrics scraping).

Reliability decisions

Fail-open vs fail-closed When a dependency (auth check, rate limiter) is down: **fail-open** (allow the request) prioritizes availability/UX but risks letting bad traffic through; **fail-closed** (block) prioritizes safety/security but can cause an outage. Fail-open for non-critical user-facing limits; fail-closed for security/correctness- critical checks (auth, payments). State which and why. [Resilience Patterns](/system-design/04-distributed-systems/13-resilience-patterns.html)
Scale up (vertical) vs scale out (horizontal) **Scale up** (bigger machine): simple, no distribution complexity, but a hard ceiling and a single point of failure — and it's how a matching engine or a single-writer system stays fast/deterministic. **Scale out** (more machines): near-unlimited scale and redundancy, but distributed-systems complexity. Scale up first (simpler) until you hit the ceiling or need redundancy; scale out for large/available systems. Note some systems (stock exchange) deliberately scale up for determinism. [Scalability](/system-design/01-foundations/11-scalability.html)

The meta-drill: “it depends on what?”

🚨 For any decision, the strong answer is never “A” or “B” — it’s “it depends on [the specific factor], and here it’s [X], so [A]. Practice completing these:

Fill in the bracket for every decision. The bracket is the trade-off.


🛠️ Try it

1. Both-sides drill. For each <details> above, state the benefit and drawback of both options without looking — then the deciding factor. If you can only argue one side, you don’t understand the trade-off yet.

2. Devil’s advocate. Take any decision you’d make and argue the opposite choice convincingly. Being able to defend both sides proves you understand the trade-off rather than parroting a default.

3. Trade-off narration in mocks. In every practice design, say the trade-off template aloud for each decision: “X gives… at the cost of… I pick X because our requirements favor…” Make it a verbal habit until it’s automatic.


Further reading