The Interview Process7 min · 15 of 64

Saying the trade-off out loud

State a design decision so it scores: the choice, the constraint behind it, the cost accepted, and the condition that would reverse it.

Trade-off articulation carries more weight than any other line on a senior system design rubric, and it is the line candidates most often leave blank. They reach the right answer and never say why, so the interviewer writes down a decision with no reasoning attached. An undefended correct choice scores about the same as a guess.

The fix is mechanical. Every non-trivial decision gets one spoken sentence with four parts.

The four-part sentence

I'm choosing X over Y because <constraint from the requirements>.
The cost is <specific downside>, which I'm accepting because <why it does not matter here>.
If <condition> changed, I'd switch to Y.

Each clause works. X over Y proves an alternative existed. The constraint proves the choice came from step one of the four-step framework — see functional vs non-functional requirements — not from habit. The cost proves you know what you bought. The reversal condition proves you hold the decision as a hypothesis rather than a belief; it is the clause almost everybody drops.

Three worked decisions

Primary datastore

An order service: 2M DAU at ~15 requests each. 2,000,000 × 15 = 30M requests/day ÷ 86,400 s ≈ 350 QPS average, and at a 3× peak about 1,050 QPS; reads are ~90%, so writes peak near 105 QPS. A commodity Postgres box handles ~5,000 simple QPS — peak is ~21% of one box.

I'm choosing Postgres over Cassandra because a multi-item order has to settle atomically and peak is ~1,050 QPS against a single-box ceiling near 5,000 — 21% utilised. The cost is that one box is both a scaling wall and a failure domain, accepted because we have ~4.7× headroom and a follower plus point-in-time recovery covers it. If sustained write QPS passed ~2,500, or a second region needed local writes, I'd shard by seller id or move to Cassandra.

Two asides worth saying aloud. ACID's C and CAP's C are unrelated: ACID-C means a transaction preserves invariants, CAP-C means every read sees the latest write. And once you add follower reads, name the lag: read-your-writes breaks, and a seller who just edited a listing reloads to the old price. See database scaling and SQL vs NoSQL.

Sync or async write path

Order confirmation does four things inline: write the order row (~5 ms), reserve inventory over a datacenter round trip (0.5 ms RTT + ~5 ms), send email through a third party (p99 ~800 ms), emit analytics. Sync p99 is 5 + 6 + 800 ≈ 811 ms, all of it the vendor.

Little's Law prices that: at 300 QPS, 300 × 0.8 s = 240 requests in flight — 240 slots held for one slow vendor. Move email and analytics to a queue and the response returns in ~12 ms: 300 × 0.012 s ≈ 4 concurrent.

I'm choosing an async email path over an inline call because the checkout p99 budget is 200 ms and the vendor alone spends 800 ms. The cost is at-least-once delivery, which means duplicates, which means the consumer needs an idempotency key on (order_id, template) — accepted, because one key on one consumer is cheap next to 240 held connections. If the requirement became "the customer sees a delivery estimate in the same response", I'd keep that call sync and leave only the send async.

Cache-aside or write-through

Redis in the same datacenter: a hit is the 0.5 ms round trip plus a ~100 ns memory lookup, so ~0.6 ms; a miss adds ~5 ms of Postgres and 0.5 ms to populate, so ~6 ms. At a 95% hit rate the mean read is 0.95 × 0.6 + 0.05 × 6 = 0.87 ms. Write-through keeps the cache warm — say 99% — giving 0.99 × 0.6 + 0.01 × 6 = 0.65 ms, at ~0.5 ms on every write. Over a 10:1 mix: 10 × 0.87 = 8.7 ms against 10 × 0.65 + 0.5 = 7.0 ms. Write-through wins on latency and is still the wrong default here.

I'm choosing cache-aside over write-through because the write path has a 99.95% availability target and cache-aside keeps Redis off it entirely. The cost is ~1.7 ms per 11 operations plus a cold-restart stampede, accepted because the read SLO is p99 < 50 ms, a 6 ms miss sits well inside it, and single-flight locking caps the stampede. If the mix went to 100:1 and the SLO tightened to p99 < 5 ms, I'd take write-through and its coupling.

The arithmetic pointed one way and the constraint the other; saying so is the signal. See caching strategies.

One-way doors and two-way doors

Not every decision earns that sentence: ninety seconds on an eviction policy is as bad as five on a shard key. Sort by reversibility.

Spend deliberation where it cannot be undone. Naming a default for a two-way door and moving on is itself a senior signal.
The door check: how much deliberation a decision deservesyesnowrong? swap it laterDecisionreachedReversible in asprint?Two-way doorpick a default, name it,move onOne-way doorenumerate 2–3 optionsScore each againsta namedrequirementCommit aloudstate the reversaltrigger

Scroll to zoom · drag to pan · 0 fits · Esc closes

One-way doors are few and predictable: the primary datastore and its shard key, the public API shape, the tenancy model, and any sync/async boundary that leaks retry semantics downstream. Everything else — eviction policy, pool size, JSON vs protobuf between internal services, queue vendor — is a two-way door.

Saying "this one is hard to reverse, so let me lay the options out properly" is itself a scoring event — it reads as someone who has migrated a shard key before.

Narrate the option space before choosing

Most candidates announce a decision then justify it. Reverse that: name two or three options, name the axis they differ on, then choose. "The write path can be sync, sync-with-outbox, or fully async; they differ on p99 and on whether consumers must be idempotent. Given the 200 ms budget, I'll take the outbox." Justifying afterwards reads as defence. Enumerating first reads as evaluation.

Disagreeing with the interviewer

A suggestion is usually a probe, not an instruction. Evaluate it in the open, then commit.

That drops the read path to a single hop, which is worth having. It moves invalidation into the write path though, and the write path carries the 99.95% target. So I'd stay with cache-aside — if writes were 10× rarer I'd take your version.

That accepts the merit, names the constraint that beats it, and offers the condition under which the interviewer is right. Flat refusal loses points; silent capitulation loses more — it shows the original reasoning was never load-bearing.

In an interview

What is being tested: whether you can hold two acceptable designs in mind and pick on evidence. Graders look for a named alternative, a requirement behind the choice, and a cost stated before they asked. Four to six of these per 45-minute round is right.

The mistake that loses points: listing pros and cons, then not choosing. "SQL is consistent, NoSQL scales, both have merits" scores zero — a rubric has no box for balance. Second worst is a cost clause with no number: "slightly slower" is an adjective, "6 ms on a 50 ms budget" is a decision. Same discipline on CAP: it is a choice made when a partition occurs, never "pick two of three", and it pairs with PACELC, which covers the ordinary case — no partition, so the trade is latency against consistency. See data consistency.

Check yourself

1. A tenant-scoped B2B app has 40,000 DAU at 60 requests each, 95% reads. Do you shard on day one? Show the arithmetic and the sentence.

40,000 × 60 = 2.4M requests/day ÷ 86,400 s ≈ 28 QPS average; at 5× peak, ~140 QPS, writes ~7 QPS. Under 3% of a ~5,000 QPS single box, so no. "I'm choosing one Postgres instance over a sharded cluster because peak is ~140 QPS against ~5,000. The cost is one failure domain, accepted because a follower covers failover and we have ~35× headroom. If sustained write QPS passed ~2,500, or a tenant needed data residency elsewhere, I'd shard by tenant id." Choose the shard key now anyway — it is a one-way door.

2. Which are one-way doors: (a) LRU to LFU eviction, (b) changing the shard key from user id to tenant id, (c) queueing the email sender, (d) returning a cursor instead of an offset from the public API?

(b) and (d). Re-keying a shard means dual-writing and backfilling the whole dataset; a public pagination contract is pinned by external clients, so reversing it costs a version and a deprecation window. (a) is a config flag. (c) is reversible for one non-critical consumer — but making the whole write path async is not: at-least-once delivery means duplicates, so every consumer assumes idempotency, and unwinding that touches all of them.

3. Rewrite this so it scores: "I'll use eventual consistency because it scales better."

"I'm choosing follower reads for the feed over leader reads because the feed has a 99.99% availability target and stale-by-seconds is fine there. The cost is that read-your-writes breaks — a user posts, hits a lagging follower, and watches their own post disappear — handled by pinning that session to the leader for 5 seconds after a write. If this path served account balances, I'd read from the leader and accept unavailability during a partition." That names the partition-time choice; the PACELC half is that with no partition, the path trades latency against consistency and takes latency.