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 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.
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 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.
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.
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.
- 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.
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".
1candidates = [declined, backend_timeout, frontend_misparse, order_never_created]2 3probe: frontend error handler log4 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 response11 declined -> status: declined12 backend_timeout -> no response logged; timeout error13 frontend_misparse -> status: success14 order_never_created -> no provider call at all15 => four different observations. run this one.16 17prediction (written first): status: declined18observed: status: success19=> 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.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Run first, predict never | Every output is explained fluently; nothing narrows | No commitment to be surprised against | Write the expectation before pressing enter, as a value |
| A vague prediction | "Something provider-related" — matched, learned nothing | Prediction chosen to be safe rather than to be falsifiable | Predict a status, a count, a branch, a line |
| Story before fact | The mismatch is reinterpreted to save the original theory | Explanation arrived before the candidate list was updated | Record "expected X, saw Y" first; shrink the list; then explain |
| Expected value copied from output | A test that passes on the day the output is wrong | The test encodes what the code does, not what it should do | Derive the expectation from the requirement or the documentation, then run |
| Convenient probe | A log line that every candidate predicts identically | Probe chosen by ease rather than by discrimination | Table 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.
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.
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.
- ?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
- 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.
- 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.
- "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.
- — 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.