Skip to content

09 · Hypothesis, then tests

When you finish this lesson you should be able to:

  • Debug with a spoken hypothesis instead of random prints
  • Notice when you are programming by coincidence
  • Add a failing case while the behavior is in your head, not at the end

Guessing is not a process. Debugging is: observe, hypothesize, predict, check, repeat. “I think parseLine drops the last field when there is no trailing newline. Next I will feed one line without \n and log the split length.” If you cannot say what you will look at, you are thrashing.

Programming by coincidence is the cousin: it worked once, so you leave it. The array happened to be sorted. The test happened to run in UTC. Coincidence is a bug that has not met the right input.

Tests are how you pin down the hypothesis. Test as you go: the moment you handle duplicates, write the duplicate case. Property-style thinking helps even without a library—“for any list, output length ≤ input length of unique ids.” You do not need a framework speech. You need a named example and an assertion.

If you are stuck for a few minutes, say it: hypothesis, smaller example, then ask. Silence while you scroll is expensive. A wrong hypothesis spoken is still progress; someone can falsify it.

Prompt: collapse events into the latest status per messageId. Tests show a failure on a hidden case.

Coincidental code: events.sort() then walk. Works if timestamps are unique and sort is stable in this runtime. Fails when two events share at and the spec says “later in the file wins.”

Hypothesis: “I’m ordering by time only, so same-timestamp pairs follow sort’s mood.” Check: two-line fixture, identical at, different status. Fix: use input index as a tie-breaker. Leave the fixture in the file.

function latestStatusByMessage(events) {
const latest = new Map();
events.forEach((event, index) => {
const prev = latest.get(event.messageId);
if (!prev || event.at > prev.at || (event.at === prev.at && index > prev.index)) {
latest.set(event.messageId, { ...event, index });
}
});
return latest;
}

You did not “add logging until it worked.” You named the rule, proved it on a small example, and kept the example.

No autocomplete. Talk.

  1. Bug report: countActive(flags) returns 1 when a flag is on for US and off elsewhere, user is in FR. Expected 0.
  2. State a hypothesis in one sentence.
  3. Invent the smallest fixture that would prove or kill it.
  4. Say the next check if that fixture passes anyway.
  5. Write one test name that would have caught this (active_respects_country_rule or similar).

Then, on a blank file, implement latestStatusByMessage for four events, including a tie on at. Narrate edges as they appear.

  • The Pragmatic Programmer — Topic 20, Debugging
  • The Pragmatic Programmer — Topic 38, Programming by Coincidence
  • The Pragmatic Programmer — Topics 41–42, Test to Code and Property-Based Testing