Unit 07 · lesson

Debugging Is an Investigation

Core path: 30 minutes

What do you do when the error message tells you where the program failed but you still do not know why?

You investigate.

Debugging is not a special mode where experienced developers suddenly know the answer. It is a process for reducing uncertainty without destroying the evidence.

Random fixes erase information

A common beginner loop looks like this:

program fails

change three things

new failure

delete a section

change two more things

maybe it works

Even if the program starts working, you may not know which change mattered.

That means you learned very little and made the next failure harder to understand.

A controlled debugging process preserves cause and effect.

Begin with a reproducible case

Suppose a rank function gives the wrong result only for a score of 95.

A useful bug report begins with:

input: 95
expected: ELITE
actual: ADVANCED

Now you can make the failure happen on demand.

If you cannot reproduce the problem, record that too. Intermittent failures require a different investigation than a deterministic one.

Observe before interpreting

Keep observation separate from explanation.

Observation:

score=95 produced ADVANCED

Hypothesis:

I think a broader branch is matching before the ELITE branch.

Those are not the same thing.

The first is evidence.

The second is an explanation you still need to test.

Localize the behavior

Ask which part of the system controls the outcome.

If the problem is the player's rank, you probably do not need to inspect JSON storage or terminal configuration first.

Find the smallest relevant boundary:

def determine_rank(score):
    ...

Then reproduce the failure there if possible.

Reducing a large failure to a small reproducible case removes unrelated noise.

Form a hypothesis that predicts evidence

Weak:

The function is messed up.

Better:

The score >= 50 condition appears before score >= 90, so a score of 95 enters the earlier branch and the later branch is never reached.

That hypothesis predicts what you should observe:

95 >= 50 → True
later condition not evaluated

You can test that prediction with a trace, a debugger, or targeted debug output.

If the evidence disagrees, update the hypothesis.

That is investigation.

Interactive model

Debug from evidence, not guesses

A useful debugging loop moves from observed symptom to evidence, hypothesis, one controlled change, and verification.

Drag nodes to inspect the relationships. Motion shows the active path; the plain background keeps attention on the relationships instead of graph-paper decoration.

View static diagramStatic Python debugging cycle

Use the cycle as a memory aid, not a ceremony. Real investigations may move backward when new evidence invalidates an earlier assumption.

Make one controlled change

Suppose you believe the branch order is wrong.

Change the order.

Do not also rename variables, rewrite the loop, change input conversion, and clean the formatting at the same time.

Then rerun the same failing case.

If the result changes in the predicted way, your evidence is stronger because the test conditions stayed controlled.

Verification includes old behavior

A repair for score 95 is not enough if it breaks score 60.

Retest:

known failing case
plus
previously passing cases
plus
important boundary cases

That is a manual regression check.

Week 14 turns the same idea into automated tests.

Debug output should answer a question

Weak:

print("HERE")
print("TEST")
print("WHY")

Those messages tell you only that execution reached some place you happen to recognize.

Better:

print(f"DEBUG score={score}")
print(f"DEBUG threshold={threshold}")
print(f"DEBUG branch=elite-check")

Even better, write the question first:

I need to know whether score is still a string before this comparison.

Then:

print(f"DEBUG score={score!r} type={type(score)}")

Temporary debug code should produce evidence, not confetti.

Minimal reproduction removes unrelated systems

Suppose a 300-line application calculates the wrong total.

Before repeatedly running the entire menu system, isolate the suspected calculation:

price = 19.99
quantity = 3
total = price * quantity
print(total)

If the defect reproduces in five lines, you now have a much smaller investigation surface.

If it does not reproduce, that tells you the missing cause may live in the surrounding data flow or state.

Both outcomes are useful.

Ask the boring questions first

Before inventing a complicated explanation, check what the system can prove quickly:

Am I running the file I edited?
What input actually reached the function?
What type is the value?
Which branch executed?
What path does the file operation use?
Which interpreter/environment is active?

The boring layer causes a surprising number of failures.

That is not glamorous. It is efficient.

Investigate one logic bug

def shipping_cost(weight):
    if weight > 0:
        return 5
    elif weight > 20:
        return 15
    return 0

Requirement:

0 or less → 0
1 through 20 → 5
over 20 → 15

Reproduction:

print(shipping_cost(25))

Expected:

15

Actual:

5

Now investigate:

25 > 0  → True

Python returns 5 immediately. The later branch is unreachable for positive values.

One controlled repair is to put the more specific higher threshold first.

Then verify 25, 20, 1, and 0.

Clean up after the investigation

Temporary prints, experimental comments, and abandoned code can become new sources of confusion.

Once the repair is verified:

  • remove temporary debug output that no longer belongs;
  • keep useful tests or reproduction cases;
  • preserve a short record of the bug when appropriate;
  • check the final diff before committing.

Debugging is finished when the evidence supports the repair and the project is back in a clean understandable state.

Vocabulary lab

Flip the idea, not just the card

Explain the term before you reveal the back. Then compare your explanation with the definition, example, and warning.

1 / 5
Read all terms without animation
Reproduce
Cause a failure to occur again under known conditions so it can be investigated consistently. Example: Running determine_rank(95) and observing the same wrong result each time. Do not confuse it with: Guessing what may have happened earlier.
Hypothesis
A testable explanation that predicts what evidence should appear if it is correct. Example: The broad threshold runs before the specific threshold, so the later branch is never reached. Do not confuse it with: A vague statement such as the function is broken.
Minimal Reproduction
A reduced example containing only the code and data needed to demonstrate a failure. Example: Testing a calculation in five lines instead of navigating the entire application. Do not confuse it with: Deleting code randomly until the failure disappears.
Regression Check
Retesting behavior that worked before a repair to make sure the change did not break it. Example: Retesting score 60 after fixing score 95. Do not confuse it with: Running only the one case that originally failed.
Controlled Change
A deliberately limited modification made so its effect can be compared against the same reproduction case. Example: Reordering two conditions without simultaneously rewriting unrelated code. Do not confuse it with: Changing many variables at once and losing cause-and-effect evidence.