Concepts / Testing Your Code Systematically

Testing Your Code Systematically

Debugging is the process of finding the cause of an error in your code, triggered by error messages or incorrect results.

  • Programming

When Debugging Begins

Every programmer encounters bugs. Debugging is the process of finding the cause of an error in your code. You need it when Python reports an error or when the program runs but produces an incorrect result. A program that does not crash is not necessarily correct: its logic may still produce an answer different from the one you intended.

Observed behaviorWhat it may meanDebugging response
An error message appearsPython has identified a problem during interpretation or executionRead the message and locate the reported part of the code
The program runs but gives the wrong answerThe program may contain a semantic error: its logic does not match the intended behaviorCompare the actual result with the intended result and trace the logic
The result matches the intended behaviorThe observed behavior is currently expectedNo debugging is needed for that behavior
startunclearevidencetoo many changescause and fix testedrestart carefullyObserved problemerror message or incorrectresultRead codecompare with intentionRun experimentchange one thingAnalyze evidenceerror type and recentchangesKnown working staterestore clarity when neededConfirmed behaviorresult matches intention
What happens next as you move from noticing an error to finding its cause, testing a fix, and confirming the program works?

Reading the Intended Logic

Reading is usually the first strategy to try because it is fast and often exposes simple mistakes. Examine the code carefully and compare what it actually does with what you intended it to do. Read the code aloud or trace it line by line. Look for a misspelled variable name, a reversed comparison operator, a missing colon, or logic that does not express the intended rule.

Tracing a decision rule

A program is intended to report that a value is eligible only when the value is at least 18. The program instead reports eligibility for a value of 16.

State the intention: The intended rule is that eligibility begins at 18.

Trace the condition: Read the comparison from the program's point of view. Ask which values the condition actually accepts, rather than assuming it expresses the intended rule.

Compare outcomes: Use the value 16 as a check. If the condition accepts 16, the code and the intended rule do not match.

Locate the mismatch: Inspect the comparison operator and its direction. A reversed comparison can make the program choose the opposite set of values.

Reading identifies a mismatch between the condition's actual behavior and the intended eligibility rule before additional changes are made.

compareinspecttracechecknoyesIntended rulewhat should happenNext lineread carefullyCurrent valuewhat data is presentActual actionwhat the line doesLogic comparisonmatches intention?Logic mismatchcandidate causeNext linecontinue trace
How can you trace code step by step to compare its actual logic with the intended behavior?

Begin with the smallest comparison between intention and behavior that can expose the bug. Reading is not passive inspection: it is a deliberate trace of the values, decisions, and actions the code produces.

Running Controlled Experiments

When reading does not reveal the problem, move to running. Make a small, deliberate change and observe what happens. Temporary display code, sometimes called scaffolding, can show the values held by variables or the intermediate results produced at different points. These observations help narrow down where the problem occurs.

Using intermediate results

A program produces an incorrect final total, but the error message does not identify which part of the calculation is responsible.

Choose an observation point: Temporarily display an intermediate value before the final total is produced.

Run the unchanged version: Record the intermediate result and the final result. This establishes what the program currently does.

Make one small change: Change only the suspected calculation or input handling, then run the program again.

Compare results: If the intermediate value changes in the expected direction, the experiment provides evidence about that part of the calculation. If it does not, focus attention elsewhere.

Remove temporary scaffolding: Once the relevant location is understood, remove temporary display code so it does not become part of the finished program.

The experiment narrows the search by showing where the actual values diverge from the intended values.

runruncompareOriginal codeObserved resultOne deliberatechangeNew observed result
How does changing and running the code affect the program's output, and how can those results reveal the problem?

Ruminating on Symptoms

Ruminating means stepping back and thinking analytically about the problem. Use your understanding of Python, the error message, the observed symptom, and the changes made recently. The goal is not to stare at the code indefinitely; it is to form a better explanation of what kind of error could produce the exact behavior you observed.

Error typeMeaningUseful question
Syntax errorThe code does not follow Python's grammar rulesWhich part of the code violates the language's required form?
Runtime errorThe code is grammatically correct, but something goes wrong while Python executes itWhat operation fails when execution reaches this point?
Semantic errorThe code runs without crashing but produces the wrong result because its logic is incorrectWhich part of the logic differs from the intended behavior?

Classifying the error narrows the set of possible causes.

classifyclassifyclassifyinspectinspectinspectsuggestssuggestsObserved symptommessage or wrong resultSyntax errorgrammar problemError messagelocation and cluePossible root causehypothesis to testRuntime errorexecution problemRecent changepossible sourceSemantic errorlogic problem
How do different error types connect to possible root causes, and how can the observed symptoms narrow the search?

Ask two questions: What kind of error could produce this exact symptom? What did I change that could cause this? Error messages often tell you what went wrong and where, while recent changes provide a focused place to begin looking.

Retreating to Clarity

Retreating is the strategy to use when many attempted changes have made the program difficult to understand. Undo the recent changes and return to a version you know works. This is not giving up. It restores a clear starting point from which you can make and test changes deliberately.

Recovering a clear starting point

A programmer has changed several calculations and added temporary displays while trying to fix an incorrect result. The program is now different from the version that originally worked, and the programmer no longer knows which changes helped or caused new problems.

Recognize confusion: The number of changes makes it impossible to connect the current behavior reliably to one specific experiment.

Return to the working version: Undo the recent changes until the program reaches a state known to work.

Re-establish the baseline: Observe and understand the working behavior before making another change.

Repeat carefully: Make one deliberate change, observe the result, and keep track of what happened.

Retreating restores clarity and makes later experiments easier to interpret.

modifytoo many changesrestorereturnrestartWorking versionknown behaviorRecent changesexperiments accumulateConfused versioncause unclearUndo changesrestore baselineControlled experimentone change at a time
How can you move from a broken program back to the last known working version while preserving a reliable recovery point?

Choosing the Next Move

Reading, running, ruminating, and retreating are tools, not fixed stages. You may use them in combination and move between them as you gather information. A good starting pattern is to read first, run a controlled experiment if reading is inconclusive, ruminate when the evidence is difficult to interpret, and retreat when accumulated changes have removed your clear baseline.

SituationStrategy to emphasizeAction
The problem looks like a simple mistakeReadingTrace the code and compare it with the intended logic
The location of the problem is unclearRunningAdd temporary display code and change one thing at a time
The symptom is difficult to interpretRuminatingClassify the error, inspect the message, and consider recent changes
Many changes have made the program confusingRetreatingReturn to a known working version and restart carefully

Choose the strategy that gives you the clearest next piece of evidence.

Practice the Four Strategies

MEDIUM

A program runs without an error message, but its final result is incorrect. You read the code and cannot immediately find the mismatch. You then add temporary displays at two intermediate points, change two calculations at once, and receive a different result but still do not know which change mattered. Finally, the program is much harder to understand than the original working version. Describe the best next actions using reading, running, ruminating, and retreating.

Hints
  • An incorrect result is a reason to debug even when the program does not crash.
  • Controlled running changes one thing at a time and uses observations as evidence.
  • When accumulated changes destroy clarity, return to a known working state.

A systematic response

Apply the four strategies to the practice situation.

Recognize the need: The incorrect result means debugging is needed even though execution completed.

Retreat: Because two changes were made together and the program has become confusing, return to the last known working version.

Read: Trace the working baseline against the intended logic, looking for a simple mismatch.

Run: If reading is inconclusive, add temporary displays and make one controlled change at a time.

Ruminate: Use the observed values, the incorrect result, and the most recent change to form and test a possible explanation.

The strongest response is not another random modification. Restore clarity, then gather evidence through reading, controlled running, and analytical reasoning.

Common Debugging Mistakes

  • Randomly changing code without a hypothesis

    You lose the connection between a change and its effect, so the result provides little useful evidence.

    Fix: Use reading, running, or ruminating to choose one deliberate change and observe its result.

  • Changing multiple things during one experiment

    You cannot determine which change fixed the problem or caused a new one.

    Fix: Change one thing at a time and keep track of what you tried.

  • Assuming no crash means no bug

    A semantic error can let the program run while producing the wrong result.

    Fix: Compare the result with the intended behavior and debug whenever they differ.

  • Ignoring the error message

    The message may provide a direct clue about the error and its location.

    Fix: Read the message carefully and use it while reasoning about possible causes.

  • Continuing to modify a confused version

    You no longer know which changes helped, hurt, or introduced the current behavior.

    Fix: Retreat to a version known to work, then proceed with smaller, trackable experiments.

Key Takeaways

  1. Debugging finds the cause of an error, whether Python reports a problem or the program produces an incorrect result.
  2. Reading compares the code's actual logic with the intended logic and is often the fastest first strategy.
  3. Running uses small experiments and temporary display code to reveal values and narrow down the problem.
  4. Ruminating classifies the error, studies messages and symptoms, and considers recent changes.
  5. Retreating restores a known working state when too many changes have made the program confusing.

Key Takeaways

  • Debugging is a systematic search for the cause of an error or incorrect result.
  • Start by reading the code and comparing its behavior with your intention.
  • Run controlled experiments with temporary displays, changing one thing at a time.
  • Ruminate on error types, messages, symptoms, and recent changes to form useful hypotheses.
  • Retreat to a known working version when accumulated changes make the program difficult to understand.