Skip to content

Testing as you go

Name your cases before you say done, then run or trace each one; a bug you catch yourself counts for you.

Predict first

Before you read: predict

Pair round on a chat app, in a plain editor where nothing runs. The candidate has written typingLine(names) to the interviewer's spec: no names, one name, two names and three or more each give a different line. She traced the interviewer's one example, two names, and got the right line.

The interviewer asks, “Are you confident this works?” Which reply gives the interviewer the most to assess?

Pick one

The 30-second version

One example that passes has checked one input on one path. Before you call the code finished, name a few cases that reach the other branches and check each: run it if you can, or trace it line by line in a plain editor. When a case goes red, say so, fix it and check that case again.

Name your cases before you say done, then run or trace each one; a bug you catch yourself counts for you.

How it works

Step 1 of 10

One run, one case. A passing example checks one input on one path; branches it never reached are still unchecked.

Analogy: A test knitter checking a new jumper pattern

Before a designer publishes a jumper pattern, a test knitter checks it. She knits the sample size and it comes out right. Before she calls the pattern ready, she picks the sizes that differ: the smallest, the largest, and the one whose lace panel has no full repeat. With no time to knit them all, she counts stitches row by row on paper: what each row says, not the shape the designer meant. When the largest size comes out two stitches short, she tells the designer, the row is fixed, and she counts it again.

With the pattern In the round
The sample size knits up right The interviewer's example passes
The smallest, the largest, the size with no full repeat Named cases: the limits and empty
Choosing those sizes before calling it ready Naming cases before "done"
Counting stitches row by row on paper A hand trace in a plain editor
Counting what each row says, not the intended shape Tracing the lines, not the intent
Two stitches short: told, fixed, counted again A caught bug said aloud, fixed, re-checked
"Ready?" answered with the sizes checked "Confident?" answered with the cases checked

Breaks down when: a pattern has a fixed list of sizes, while code meets inputs nobody listed. Wrong inference: "if my cases pass, every input is safe." The designer also reads her full report later, while an interviewer sees only the checks you show during the round.

In real terms: before "done", say three cases and check each one, out loud.

Examples, run for real

4.1 ExamHall EXH-3140: the third named case goes red

In a practice pair round, the candidate built the AnswerEditor's autosave: input(text) saves the latest text 800 ms after the last key, and the editor calls dispose() when it closes. The interviewer's example passed. Before saying done, she named three cases: rapid typing, a single key, and dispose before the save fires.

4.1 EXH-3140 draft · the interviewer's example (keys at 0, 200, 400 ms): saves "Dem" at 1200 ms · pass
4.1 EXH-3140 draft · named cases: 2 of 3 pass · red: dispose before it fires (saved "D" at 800 ms, 300 ms after dispose at 500 ms)
4.1 MR !3440 dispose clears the scheduled save · named cases: 3 of 3 pass

The example could never have shown this: nothing in it calls dispose(). The draft's dispose() set its timer variable to null without clearing the scheduled save (steps 1–3). She said “dispose doesn't cancel the save”, fixed it, and ran the three cases again (step 8). How a debounced save is written belongs to kata-debounce.

4.2 Chat, MR !3441: a hand trace of trimHistory

export function trimHistory(messages, max) {
  return messages.slice(-max);
}

The chat app keeps the last max messages of a thread. With no runner, the candidate traces the case max = 0, the limit, on a thread of 5:

4.2 trimHistory(thread of 5, 2) · slice(-2) · 2 messages kept · spec 2 · pass
4.2 trimHistory(thread of 5, 0) traced line by line
4.2   max = 0 · -max = -0
4.2   messages.slice(-0) is messages.slice(0) · 5 messages kept
4.2   returned 5 messages · spec 0 · red
4.2 MR !3441 (return [] when max <= 0) · max 0: 0 kept · max 2: 2 kept · pass

Read as intent, slice(-max) "takes the last none". Traced line by line, -0 is 0, and slice(0) keeps everything (steps 6–7).

Try it

Answer first, then check. Nothing is saved.

Question 1 of 7

The chat app's spec: a message joins the bubble above it when it has the same sender and was sent 300 seconds or less after the message above it; otherwise it starts a new bubble. at is in seconds. The candidate's code:

The interviewer's example, Ana at 0, Ana at 60 and Ben at 90, gives two bubbles, as the spec says. Before saying done, she names four cases.

Which one fails on this code?

Code for this question
// bubbles.mjsexport function groupBubbles(messages) {  if (messages.length === 0) return [];  const groups = [[messages[0]]];  for (let i = 1; i < messages.length; i++) {    const prev = messages[i - 1];    const m = messages[i];    if (m.from === prev.from && m.at - prev.at < 300) {      groups[groups.length - 1].push(m);    } else {      groups.push([m]);    }  }  return groups;}
Pick one

Question 2 of 7

No runner in this round. The candidate's code:

The thread holds six messages: ids 1 to 6, sent by ben, ana, ben, ben, ana, ben. She traces the call unreadCount(thread, 3, 'ana') line by line.

What does the trace give?

Code for this question
// unread.mjsexport function unreadCount(messages, lastReadId, me) {  const i = messages.findIndex((m) => m.id === lastReadId);  return messages.slice(i).filter((m) => m.from !== me).length;}
Pick one

Question 3 of 7

Minute 28 of 40 in a chat-app round. Tracing her named case /mute 0m, the candidate sees her parseMute mute the user for the default hour, where the spec says 0 minutes is rejected. The interviewer has not noticed.

What does she do?

Pick one

Question 4 of 7

A chat-app round in a shared plain editor; nothing can be executed. The candidate has written mentionsToNotify(text) and named three cases with the result the spec gives each.

The interviewer says, “We can't run code here.” What is her next step?

Pick one

Question 5 of 7

The interviewer's spec: mentionsToNotify(text, members, sender) returns, in order of first mention, each member named as @name in the text, once, leaving out the sender and any name that is not a member. Names are lower case.

Before you call it done, write your cases: at least four, each with its input and the result the spec gives.

Question 6 of 7

A chat-app pair round transcript. At 21:40 the candidate finishes groupByDay(messages) and says, “Done, that should work.” At 21:50 the interviewer asks, “How would you test it?” She names an empty thread, a message just before midnight and one just after, and traces the midnight pair: both land on the right days.

What was missing from her round?

Pick one

Question 7 of 7

The chat app's spec: topReaction(reactions) returns the emoji that the most different people used; on a tie, the emoji used first; null when there are no reactions. The candidate's code:

She names three cases before saying done.

Which one fails on this code?

Code for this question
// top.mjsexport function topReaction(reactions) {  const seen = new Set();  const counts = new Map();  for (const r of reactions) {    if (seen.has(r.user)) continue;    seen.add(r.user);    counts.set(r.emoji, (counts.get(r.emoji) ?? 0) + 1);  }  let best = null;  for (const [emoji, n] of counts) {    if (best === null || n > counts.get(best)) best = emoji;  }  return best;}
Pick one

Go deeper

Pro tip

In practice rounds, write your cases as one comment line above the function before you write its body; tick each one off only after you have run or traced it.

Checked against the tools and docs as of 2026-10-06.