ExperimentsGENERALILLUSTRATIVETEAM-SPECIFIC

Prediction Before Execution

Before you run the code, say what you expect to see. If the output matches, you understood the system; if it does not, you have just found the exact place your model of it is wrong — and that is information you can only get by having committed to a prediction first. It is the cheapest experiment in engineering and the most skipped.

The moveWorked exampleNext questions▶ Debugging Lab

The situation, the reflex, and why it stalls

Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.

The question

You are about to run something — a test, a query, a request, a debugging change. What do you expect to happen, and what will you learn if it does not?

The situation

The frontend says "Payment failed". You have a theory, or several. You are about to add a log line, run the request again, and look. A colleague asks what you expect the log to say, and you notice that you have not decided — you are going to run it and then form an opinion about whatever appears.

The reflex

Run it and see. Looking is fast, the output is right there, and forming an opinion afterwards feels like the natural order: evidence, then belief. It is the reflex of someone who trusts the system to tell them what is going on.

Why it stalls

Whatever appears is explained. The log says the provider returned a status you did not expect, and within a second there is a story for why that makes sense — the story would have been just as fluent for any other status. Nothing was learned, because nothing could have surprised you.

What the reflex produces — and fails to produce
  • Whatever appears is explained. The log says the provider returned a status you did not expect, and within a second there is a story for why that makes sense — the story would have been just as fluent for any other status. Nothing was learned, because nothing could have surprised you.
  • The wrong probe is chosen. Without a prediction there is no way to know which observation would distinguish the candidate causes, so the log line goes wherever is convenient and reports something true of every candidate.
  • Debugging becomes a sequence of looks rather than a sequence of eliminations. Each run produces output, each output produces a theory, and the theories do not narrow because none was tested against a commitment.
  • The same mechanism poisons tests: a test is written after the code, its expected value is copied from the output, and it passes forever — including on the day the output is wrong.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Before executing anything whose outcome you care about, write down — literally, in the notebook or the terminal — what you expect: the value, the status, the count, the log line. Then run it. Then compare. The comparison has two outcomes and both are valuable: a match confirms your model at that point; a mismatch locates, precisely, where your model of the system diverges from the system.
  • Make the prediction specific enough to be wrong. "It will fail" is not a prediction; "the provider call will return a declined status and our handler will map it to Payment failed" is, because the log can now say "the provider returned success" and you have learned that the failure is on your side of the boundary.
  • Use the prediction to choose the probe. If two candidate causes predict the same observation, that observation is worthless for telling them apart; pick the one where the candidates disagree (Binary Search Over the System). A prediction is what makes "which log line?" answerable.
  • When the prediction fails, resist the fluent story. Write the mismatch down as a fact — "expected declined, saw success" — and let it change the candidate list before you explain it. The story can come after the list has shrunk.

Candidates, probes and what each predicts

The table is the debugging lab's core idea written by hand: for each candidate cause, what would this probe show? A row where every candidate predicts the same thing is a probe not worth running. A row where they all differ is the one to run first. The prediction is what makes the table possible; without it there is only "add a log and look".

"Payment failed" — candidates against probes
1candidates = [declined, backend_timeout, frontend_misparse, order_never_created]
2
3probe: frontend error handler log
4 declined -> "Payment failed"
5 backend_timeout -> "Payment failed"
6 frontend_misparse -> "Payment failed"
7 order_never_created -> "Payment failed"
8 => distinguishes nothing. skip.
9
10probe: backend log of provider response
11 declined -> status: declined
12 backend_timeout -> no response logged; timeout error
13 frontend_misparse -> status: success
14 order_never_created -> no provider call at all
15 => four different observations. run this one.
16
17prediction (written first): status: declined
18observed: status: success
19=> candidates = [frontend_misparse]. now, and only now, the story.

The skipped probe is the lesson's honest half: some evidence is worthless, and only a prediction tells you which before you spend the time gathering it.

How the prediction goes missing

Each row is a way the move is skipped by someone who believes they are doing it. The symptoms are subtle because in every case output was looked at and a conclusion was reached; what is missing is the moment where the conclusion could have been wrong.

Prediction, skipped or faked
TriggerSymptomCauseResponse
Run first, predict neverEvery output is explained fluently; nothing narrowsNo commitment to be surprised againstWrite the expectation before pressing enter, as a value
A vague prediction"Something provider-related" — matched, learned nothingPrediction chosen to be safe rather than to be falsifiablePredict a status, a count, a branch, a line
Story before factThe mismatch is reinterpreted to save the original theoryExplanation arrived before the candidate list was updatedRecord "expected X, saw Y" first; shrink the list; then explain
Expected value copied from outputA test that passes on the day the output is wrongThe test encodes what the code does, not what it should doDerive the expectation from the requirement or the documentation, then run
Convenient probeA log line that every candidate predicts identicallyProbe chosen by ease rather than by discriminationTable the candidates against the probe; run only rows that differ

Looking versus predicting

The pair below is the same debugging session, with and without a commitment. The second is slower by one sentence per run and faster by every run it avoids.

The same session
Run and look
Add a log in the frontend handler. It says Payment failed. Add a log in the backend. It shows a success status — "huh, maybe the frontend is wrong, or maybe the provider sends success for pending?" Add a log for the provider's raw body. Read it. Form a theory. Restart the provider mock to see if it changes. It does not. Twenty minutes, four probes, one theory that was not tested.
Predict and run
List four candidates. Table them against the frontend log: all identical, skip it. Table them against the backend log: all different, run it. Predict declined. See success. Candidate list is now one. Check the story — the response field changed — against the provider's changelog. Ten minutes, one probe, one confirmed cause.

The prediction converted the backend log from "a place to look" into a test with four possible outcomes each mapped to a cause. The looker gathered more evidence and knew less, because none of it could have contradicted anything; the predictor gathered less and knew more, because one observation was allowed to be wrong.

How to do it

Most important first.

  • Say the prediction out loud or write it before pressing enter. It feels silly for about a week; the day it saves is the day the output surprises you and you notice that it did.
  • Predict a value, not a feeling. Status code, row count, which branch, which log line, roughly how long.
  • For each candidate cause you are carrying, write what it predicts for the probe you are about to run. If they all predict the same thing, choose a different probe (Predict Before You Look).
  • When the prediction fails, update the candidate list first and the explanation second. The order matters; the explanation is where the fluent story sneaks in.
  • Apply it to tests: write the expected value from your understanding before running the code, and when the test fails on first run because the code is right and the expectation was wrong, treat that as the test doing its job on you (Invariants as Tests).
  • Apply it to AI answers and to unfamiliar code the same way: predict what the function returns before reading its body, predict what the model's code does before running it; the mismatch is where the understanding was borrowed instead of owned.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • "Payment failed" on the frontend. Candidates: the provider declined; our backend timed out waiting for the provider; the backend succeeded but the response was mis-parsed by the frontend; the order was never created so the charge was never started. Four causes, one symptom.
  • The tempting probe is a log line in the frontend's error handler. Prediction per candidate: all four show "Payment failed" there. The probe distinguishes nothing; it is not run. Next candidate probe: the backend's log of the provider's response. Predictions: declined shows a declined status; timeout shows no response logged; mis-parse shows a success status; never-created shows no provider call at all. Four candidates, four different observations — this probe is worth running.
  • Prediction written: "declined status, because the test card is the decline card." Run. Observed: the provider returned success, and the log shows it. Mismatch — recorded as a fact before any story. The candidate list is now one: the frontend mis-parsed a successful response. The story arrives afterwards and is checked, not assumed: the response shape changed in the provider's new API version and the frontend reads the old field.
  • The fix is one line, and the test written for it has an expected value written from the provider's documentation, not copied from the output — so that the next API change fails the test rather than silently passing it.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • There is a prediction in the notebook or the terminal history with a timestamp before the run, for every run you cared about.
  • You have recently been surprised by an output and can say exactly what you believed that turned out false.
  • You have declined to run a probe because every candidate predicted the same result.
  • Your tests have expected values you could defend without looking at the code's output.

The questions you can now ask

The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.

Next questions
  • ?Before I run this, what exactly do I expect to see?
  • ?What does each candidate cause predict for this probe, and do any two predict the same thing?
  • ?If the result does not match, which of my beliefs about the system was wrong?
  • ?Is this expected value something I derived, or something I copied from the output?

What can go wrong

How the move itself fails
  • The prediction is made vague enough to always match. "Something about the provider" matches every log; a prediction that cannot fail is the reflex with extra steps.
  • The mismatch is explained before it is recorded, and the explanation happens to preserve the original theory. The discipline is: fact first, candidate list second, story third.
  • Prediction is applied to runs whose outcome nobody cares about — every save, every reload — and becomes a tic. It is for runs that are supposed to tell you something.
  • A matched prediction is taken as proof of the whole model. It confirms the model *at that probe*; the next probe may still surprise you. Confidence should grow per matched prediction, not jump to certainty.
What the move costs
  • Predicting before every meaningful run is slower by a sentence each time, and on a good day most predictions match and feel unnecessary.
  • A prediction commits you publicly — to a colleague, or to your own notebook — to a belief that may be wrong, and being visibly wrong is the cost that makes people skip the move.
  • Choosing probes by prediction sometimes means not running the convenient probe, and setting up the discriminating one takes longer.
Misreads
  • "Evidence first, then belief — that is the scientific order." The scientific order is hypothesis, then evidence. Belief formed after the evidence is not tested by it; the prediction is what turns looking into an experiment.
  • "This only applies to debugging." It applies to any execution whose result you will interpret: a query, a load test, a migration, a model's generated code, an unfamiliar function. Debugging is where it pays fastest because the alternative — changing things and looking — is so seductive there.
  • "A senior engineer just knows where to look." A senior engineer is running predictions so quickly it looks like knowing. The visible version — written down — is how the speed is built, and the written version is still what they do when the problem is hard.

Where this applies

Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.

  • GENERALCommit to an expectation, then observe, then compare — this is the same move in debugging, in testing, in reading unfamiliar code, in reviewing generated code and in running any experiment; only what is predicted changes.
  • ILLUSTRATIVEThe four candidate causes, the decline test card and the provider's changed response field are invented to show a prediction failing usefully; no real provider's API is being described.
  • TEAM-SPECIFICOn a team the prediction is said to a colleague before the run and the mismatch is a shared fact; a solo engineer has to write it down, because a prediction held only in the head is rewritten by the output the moment it appears, and the notebook is the only witness.

Where the depth lives

This domain asks the question and hands the answer off by name.

Observability & Performancemeasure-before-optimizing
Further
  • The manifesto's "review the LLM's answer" at /manifesto/review begins with the same move: predict what the generated code will do before running it, and treat the mismatch as the place where understanding was borrowed rather than owned.