Skip to content

06 · Design it twice, name the trade-off

When you finish this lesson you should be able to:

  • Produce two plausible designs before you fall in love with one
  • State a trade-off in one sentence: what you gain, what you pay, what you watch
  • Keep designs reversible when the important fact is still unknown

The first design you think of is a draft. Design it twice: a second approach, even if crude, exposes the real decision. If both designs share a box, that box is probably load-bearing. If they disagree, that disagreement is the interview.

Good design is not maximal cleverness. It is orthogonality (a change in targeting does not rewrite delivery) and reversibility (you can change your mind about the store without rewriting the product). When a decision is expensive to reverse (which field is the source of truth for “this charge happened”), slow down and name it. When it is cheap (JSON vs protobuf inside a service you own), pick and move.

You will not have all the numbers. Say the assumption (“we are under 50 messages per user per day”), pick, and name the metric that would force a revisit (“if we queue millions of fan-outs, this in-process buffer dies”). Judgment under incomplete information is the skill. Silence while you wait for a perfect spec is not.

Tracer bullets help: a thin path through the real pieces (client → API → store → one worker) that you can extend, instead of a pile of mocks that never meet.

Two designs for “notify when a card fails.”

Design A — synchronous. The payment worker calls notify() before it returns. Simple. Cost: payment latency includes the email provider. Watch: p95 of the worker, provider timeouts.

Design B — outbox. The worker writes card_failed and a outbox row in one transaction. A notifier drains the outbox. Cost: more moving parts, at-least-once delivery. Watch: outbox lag, duplicate emails (so the notifier must be idempotent).

Do not present only B because it sounds “distributed.” If volume is low and the provider is in-region, A plus a short timeout may be right. The senior sentence is: “I’d start with A because X; I’d move to B if Y.”

flowchart LR
  subgraph designA [Design A]
    payA[Payment worker] --> notifyA[Notify now]
  end
  subgraph designB [Design B]
    payB[Payment worker] --> outbox[Outbox]
    outbox --> notifyB[Notifier]
  end

Out loud, no slides.

  1. Prompt: “Users can snooze a message for 24 hours.”
  2. Sketch design A (store the snooze on the message).
  3. Sketch design B (a per-user mute window in a policy service).
  4. Name one thing each design makes easy, and one thing it makes miserable.
  5. Pick. Say what you would watch for a week after ship.
  6. Say what would make you reverse the pick.

If you cannot name a watch, you did not finish the trade-off.

  • A Philosophy of Software Design — Chapter 11, Design it Twice
  • The Pragmatic Programmer — Topic 8, The Essence of Good Design
  • The Pragmatic Programmer — Topics 10–12, Orthogonality, Reversibility, Tracer Bullets