How Candidates Fail

Microservices at minute five, designing for a billion when asked for a thousand, silent whiteboarding, invented numbers, and no commitment.

The failure modes are a short list, and they repeat. Almost all of them are process failures rather than knowledge failures, which is why strong engineers hit them.

The list

  • Microservices at minute five. Splitting the system before any requirement asks for a split. One stateless service is the correct opening answer; you can split it later, on the board, for a stated reason.
  • Designing for the wrong scale. A thousand users asked for, a billion designed. Sharding and multi-region for a load that fits on one machine is as wrong as missing a bottleneck, and more revealing, because it proves the design did not come from the numbers.
  • Silent whiteboarding. Five minutes of drawing with no narration. There is nothing in a silence to score.
  • Inventing numbers. Quoting a figure you cannot derive. Show the arithmetic — "200M daily users, 50 reads each, about 115k read QPS" — so a wrong input is visibly a wrong input rather than a wrong candidate.
  • Refusing to commit. Listing three options and waiting for approval. Pick one, name its cost, move on. The interviewer will not choose for you, and the clock is yours to lose.
  • Defending past the evidence. They have shown you the choice breaks and you keep arguing. Conceding the point and naming the one box that changes costs you a decision; arguing costs you the round.

The common root

Every item above is a symptom of answering from memory instead of from requirements. A memorised design cannot survive one changed constraint, and changing a constraint is the cheapest question in the interviewer's set — they will ask it, and the answer is where the hiring decision gets made.

Example

Example · javascript
// A self-review pass for your own practice answers. Each check is a failure
// mode that interviewers see in almost every weak round.
const CHECKS = [
  { id: 'scale-mismatch',
    failsWhen: (d) => d.services > 1 && d.dau < 100_000,
    says: 'Multiple services below 100k DAU: justify the split or collapse it to one.' },
  { id: 'premature-sharding',
    failsWhen: (d) => d.shards > 1 && d.writeQps < 5_000,
    says: 'Sharded under 5k write QPS: one primary still absorbs this.' },
  { id: 'unsourced-number',
    failsWhen: (d) => d.numbers.some((n) => !n.derivedFrom),
    says: 'A figure with no arithmetic behind it. Show inputs x rate = result.' },
  { id: 'no-commitment',
    failsWhen: (d) => d.decisions.some((x) => !x.choice),
    says: 'An option list with no choice is not an answer.' },
  { id: 'uncosted-choice',
    failsWhen: (d) => d.decisions.some((x) => x.choice && !x.cost),
    says: 'A choice with no stated cost reads as a guess.' },
];

// Longhand: function audit(d) { return CHECKS.filter(function (c) { return c.failsWhen(d); })
//                                            .map(function (c) { return c.says; }); }
// Shorthand: the arrow form below is the same function (arrow functions, ES2015).
const audit = (d) => CHECKS.filter((c) => c.failsWhen(d)).map((c) => c.says);

audit({ dau: 2_000, writeQps: 30, services: 6, shards: 4, numbers: [], decisions: [] });
// -> flags scale-mismatch and premature-sharding: the classic over-design

When to use it

  • Self-scoring a recorded mock interview before the real round
  • Working out why a candidate who clearly knows the material keeps failing
  • Deciding when to concede a point the interviewer has already disproved
  • Checking an answer for components the agreed scale does not justify

More examples

Numbers with the arithmetic left visible

Stating the inputs turns a challenged number into a corrected number rather than a lost argument. Use it for every figure you put on the board, including the ones you are confident about.

Example · javascript
// Invented figures are the fastest way to lose credibility, and the fix is
// mechanical: never state a result without its inputs.
const estimate = (label, inputs, compute) => ({
  label,
  inputs,
  value: compute(inputs),
  // The string is the point: it survives a wrong input, because the interviewer
  // can correct the input instead of distrusting the answer.
  derivedFrom: Object.entries(inputs).map(([k, v]) => `${k}=${v}`).join(' x '),
});

const readQps = estimate(
  'read QPS',
  { dau: 200_000_000, readsPerUserPerDay: 50, secondsPerDay: 86_400 },
  ({ dau, readsPerUserPerDay, secondsPerDay }) =>
    Math.round((dau * readsPerUserPerDay) / secondsPerDay), // ~115,700
);

// "If daily actives are 20M rather than 200M, that is 11.5k read QPS and the
// cache tier is one node" — the same sentence, one input changed.

Conceding a point cleanly

A clean concession has four parts: acknowledge, name the smallest change, name its cost, say what stays. Rehearse it, because in the moment the instinct is to defend.

Example · javascript
// The interviewer has just shown that the chosen partition key makes one shard
// hot. There is no version of defending this that scores better than fixing it.
const concede = {
  acknowledge: 'You are right — partitioning by user_id puts every post from one ' +
               'large account on a single shard.',
  // Name the smallest change, not a new design.
  change: 'Partition by (user_id, bucket) where bucket is derived from the post timestamp.',
  cost: 'A single-user timeline read now fans out across buckets instead of hitting one shard.',
  keep: ['the service tier', 'object storage for media', 'the cache in front of reads'],
  nextStep: 'I would measure per-shard write rate before picking the bucket width.',
};

// What loses the round is the fourth minute of arguing, not the original mistake.

Ending silence with a narration habit

Candidates do not fail for thinking slowly, they fail for thinking inaudibly. Record one mock round and count the silences longer than ten seconds; these phrases are what goes in them.

Example · javascript
// Silent whiteboarding is the easiest failure to fix and the hardest to notice.
// A phrase per situation, so the pause has words in it.
const narration = {
  thinking: 'I am deciding whether this read can be stale, because that decides the next three boxes.',
  drawing:  'Drawing the write path first: client, load balancer, service, primary.',
  stuck:    'Two options here and I do not have the number that picks between them — can I assume reads dominate?',
  pivoting: 'That changes the constraint, so I am replacing the fan-out worker and keeping everything else.',
  closing:  'The single points of failure are the primary and the cache; here is what I would do about each.',
};

Discussion

  • Be the first to comment on this lesson.