When a program fails: hunt two bugs by reasoning
Hands-on project: build a small buggy program, drive it into one loud crash and one silent wrong answer, then locate each cause by reasoning and prove the fix.
Reading about failures is not the same as hunting one down. Build a small program, plant two failures of opposite character — a loud crash and a silent wrong answer — then use the unit’s method to locate each cause by reasoning, not guessing, and prove the fix with evidence.
Turn the unit’s mental model into a repeatable engineering loop: distinguish a loud defined error from a silent failure in your own code, read a real stack trace top-down to the throw site, separate where an error surfaced from where its cause lives, and locate each bug by a written hypothesis confirmed against actual state.
Build a small program with two deliberately planted bugs — one that raises an exception (a loud, defined error) and one that produces a wrong answer with no error at all (a silent failure) — then locate and fix each by reasoning, documenting the hypothesis and the evidence at every step.
- A short write-up that, for each bug, states the hypothesis, the state you checked, the observation, and the one-line fix — proving you located the cause by reasoning, not by changing lines until the symptom vanished.
- For bug A: the annotated stack trace with the throw site and entry clearly marked, and a sentence on whether the throw site was the cause or whether the real cause was upstream in the call chain.
- For bug B: a clear statement of why no exception was raised — what made the failure silent — and the before/after output showing the wrong answer becoming the right one.
- A one-line classification of each bug as a defined error or a silent failure, with the reason.
- Add a third bug that is an out-of-bounds read, and describe how the same bug would behave as a defined error in a bounds-checked language versus undefined behaviour in a low-level unchecked one.
- Wrap bug A's throwing call in a handler (a catch block) one frame up, recover gracefully, and show the program now continues instead of crashing — demonstrating that an exception is not the same as the program being broken.
- Convert the silent failure into a loud one by adding a validation check that throws on the bad value, and argue in two sentences why turning a silent failure into a loud, defined error is usually an improvement.
- Write a one-page debugging runbook for a beginner: the three moves (narrow the range, read the trace, check state), how to read a trace top-down, and the rule that the throw site is where the error surfaced, not always where the cause lives.
This is the loop you will run on every real failure: classify it — loud defined error or silent wrong answer — read the trace top-down to the throw site, then separate where it surfaced from where the cause lives. Locate the cause by reasoning over a deterministic machine: write a testable hypothesis, narrow the range, check the actual state, and change a line only once you have proven the guilty step. Doing it once on a toy program with two planted bugs makes the production version muscle memory.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.