Thinking Out Loud

Name the options, name the constraint that decides, state the choice, state the cost — and answer "why not X" without a rewrite.

Syntaxoptions -> deciding constraint -> choice -> cost (-> what would change it)

The interviewer is scoring a process they can only see if you narrate it. Four moves, in order, for every decision that matters:

  1. Name the options. "Either I precompute each timeline on write, or I merge the sources on read."
  2. Name the constraint that decides. "Reads outnumber writes by about 65 to 1, and the feed p99 target is 200ms."
  3. State the choice. "So: fan out on write."
  4. State what it costs. "A post from an account with ten million followers becomes ten million cache writes, so very large accounts need a read-path exception."

The fourth move is the one candidates skip, and it is what separates a choice from a guess. Naming the cost yourself also pre-empts the follow-up, because the interviewer was about to name it for you.

Handling "why not X?"

"Why not X" is usually a probe, not a correction. Answer it with the constraint rather than with a rewrite: "X would work — it loses on the staleness requirement we agreed, because the index would be rebuilt in batches." Candidates who hear it as a rejection start erasing boxes, and twenty minutes later there is no design on the board at all.

When to actually reverse

Change your mind when you are handed new information, and say that is what happened: "if writes are the larger number, this flips to merging on read — should I assume that?" Reversing with a stated reason reads as engineering. Reversing because the interviewer frowned reads as having had no reason in the first place.

Example

Example · javascript
// The four moves, as a record you can actually keep. This is also what a design
// doc's decision log looks like, which is why practising it is not wasted effort.
const decisions = [
  {
    question: 'How is the home timeline built?',
    options: ['fan out on write (precompute per follower)', 'fan out on read (merge at query time)'],
    constraint: 'reads outnumber writes ~65:1 and the feed p99 target is 200ms',
    choice: 'fan out on write',
    cost: 'one post by a 10M-follower account becomes 10M cache writes',
    mitigation: 'accounts above a follower threshold are excluded and merged on read',
    revisitIf: 'write amplification, not read latency, becomes the binding limit',
  },
  {
    question: 'Where do uploaded images live?',
    options: ['bytes in the database', 'object storage with the URL in the database'],
    constraint: 'originals are 2-8MB and are served far more often than they are written',
    choice: 'object storage, URL in the row',
    cost: 'two systems to keep consistent — a row can point at an object that failed to upload',
    mitigation: 'write the row only after the upload completes; sweep orphaned objects in a batch job',
    revisitIf: 'images become small and strictly transactional with the row',
  },
];

// Say every field out loud. An unspoken `cost` is a cost the interviewer assumes
// you did not know about.
const narrate = (d) =>
  `${d.options.join(' or ')}. ${d.constraint}. So ${d.choice}. That costs: ${d.cost}.`;

When to use it

  • Narrating a datastore choice so the interviewer can follow the reasoning
  • Answering "why not Kafka here?" without abandoning your design
  • Reversing a decision mid-interview when a new number arrives
  • Writing a decision log entry your team can audit six months later

More examples

Answering "why not X" without rewriting

Every one of these ends with the condition under which the interviewer's suggestion becomes correct, which is the answer to the question they are really asking. Rehearse the three-part shape rather than memorising the sentences.

Example · javascript
// Prepared answers are not scripts; they are a reminder to reply with the
// constraint you already agreed, instead of reaching for the eraser.
const whyNot = {
  'Why not a graph database for the follow edges?':
    'It fits the model, but the query here is one hop — followers of a user — and the relational ' +
    'index already answers that. I would reach for it when the queries go multi-hop.',
  'Why not Kafka instead of a simple queue?':
    'If consumed events must be re-read later, yes. Nothing in the agreed scope replays, and ' +
    'the queue is one managed component instead of a cluster to operate.',
  'Why not shard from day one?':
    'The write rate is ~4.6k/sec peak, which one primary absorbs. Sharding now costs cross-shard ' +
    'reads for no current benefit. The trigger to shard is the primary saturating on writes.',
};

// Each answer has the same three parts: concede the option is real, name the
// constraint that rules it out here, name the condition that would bring it back.

A reversal that scores well

A reversal is a chance to show the design was built from constraints rather than from memory. State which boxes move and which stay put, so the change is a two-minute edit instead of a restart.

Example · javascript
// Minute 28. The interviewer says: "actually, assume 90% of traffic is writes."
// That is new information, so the decision changes — out loud, with the reason.
const before = { choice: 'fan out on write', because: 'reads dominate ~65:1' };

const after = {
  choice: 'fan out on read, with a cache in front of the merge',
  because: 'the read:write ratio you just gave inverts the constraint that decided this',
  changedBoxes: ['timeline-cache becomes a query-time merge', 'the fan-out worker is removed'],
  unchanged: ['the follow graph', 'object storage for media', 'the load balancer and service tier'],
};

// Naming what did NOT change is half the signal: it shows the design was
// modular, and keeps a 20-minute rewrite from starting.

Discussion

  • Be the first to comment on this lesson.