Python Programming · Foundations
Debugging
On this page 9 sections
In 30 seconds
debugging A deliberate investigation of why a program's observed behavior differs from the intended behavior. Full entry → is the deliberate process of finding why a program does not behave as intended, then changing and checking one thing at a time. Python helps by reporting syntax errors before code runs and by showing a traceback Python's report of the call sequence and source locations associated with an unhandled exception. Full entry → for an unhandled runtime exception An event during execution that interrupts normal control flow unless code handles it. Full entry →. A program can also run without crashing yet calculate the wrong result; that is a logic error A defect in which code runs but produces behavior or a result that violates its intended rule. Full entry →, and it needs evidence such as a small example or an inspected intermediate value.
Why this matters
Debugging turns a vague report—“it does not work”—into a checkable question. In coursework, it helps you explain a failure with the code path and values that produced it instead of guessing at a fix. In practical programs, a short reproducible case and a careful traceback can reveal whether the problem is invalid input, a mistaken assumption, or control flow that never reaches the intended line. The same habits support later testing, but debugging is investigation rather than proof that all cases work.
The college version
Classify the observed failure before choosing a tool
Debugging begins with an observation, not a tool. If Python cannot parse the program, it reports a syntax error A problem in code structure that Python detects while parsing the source before normal execution. Full entry → and identifies a location in the source. For example, if True print("hello") cannot be compiled because the conditional header lacks its required colon. That error is different from a runtime exception: 1 / 0 is valid Python syntax, but executing it raises ZeroDivisionError. The program began running and encountered an operation it could not complete.
A third category is often called a logic or semantic error. The program executes, but its output does not meet the program's intended rule. A function meant to compute an average might accidentally return the sum. Python cannot infer that the programmer wanted a different calculation, so no traceback is guaranteed. The debugging evidence must instead compare an expected result with the actual result and inspect the steps between them. Keep this classification practical: a single defect can expose more than one symptom, but naming the first observable kind of problem helps choose the next check.
Treat a traceback as a route through the failing call
When an exception is unhandled, Python prints a traceback. Read it from the bottom first: the final line names the exception type and usually supplies its message. Then move upward through the listed calls to see how execution reached that line. A file name and line number identify source locations; they are clues to inspect, not a substitute for understanding the values and assumptions at that point.
Suppose a function divides a total by len(values). Calling it with an empty list produces ZeroDivisionError: division by zero. The immediate expression failed, but the useful debugging question is also why an empty list was allowed to reach the function. Check the call site, the input, and the function's contract. Do not respond by broadly hiding the exception; designing try/except behavior is the separate Exceptions lesson. Here, use the traceback to form a specific hypothesis, then make the smallest safe observation that could confirm or reject it.
Use a controlled investigation loop
A reliable loop is: reproduce Run a defined case again so an observed behavior can be compared before and after a change. Full entry → the behavior, state one hypothesis, gather one relevant observation, make a focused change, and run the same case again. Reproduction matters because a one-off symptom is hard to compare after a change. If the full program is noisy, reduce it to the smallest program and input that still displays the behavior. Reduction is not merely shortening code; it preserves the behavior you are trying to explain while removing unrelated paths. Keep a record of the exact input, command, and observed output, because an investigation is much harder to repeat when its starting conditions are guessed later. A written observation is useful only when it is specific enough that someone else could check it.
For a small program, an intentional print() can expose an intermediate value, a branch decision, or the size of a collection. Print labels as well as values so output remains interpretable: print(f"count={len(values)} total={total}"). Remove or replace temporary inspection after the question is answered, since debugging output can obscure normal output. For a failure involving repeated calls or nested functions, pdb is Python's standard-library debugger. It can pause at a breakpoint A chosen pause point where a debugger stops execution so program state can be inspected. Full entry →, step through source lines, inspect stack frames, list source, and evaluate expressions in the selected frame. That makes it useful when another print statement would not reveal which call or frame contains the unexpected state.
A fix is not finished when an error message disappears. Re-run the original failing case and at least one nearby case that should still work. If changing a denominator check makes the empty-list case clear but breaks ordinary lists, the investigation has uncovered a new defect rather than completed the old one. This lesson focuses on diagnosis; systematic test-suite design belongs to Basic Testing.

Eli explains
The same idea, in plain words
Explain it like I’m 10
Debugging is like being a careful investigator for a program. First, make sure you can see the same problem again. Then ask one small question, such as “What number reached this division?” A traceback is the program's trail of footprints: it shows where it was called and the last place where it could not continue. A print statement is like writing down what you find at one checkpoint.
Sometimes the program follows every instruction but reaches the wrong answer. That is not necessarily a crash; it is like following a recipe that says to add the ingredients but forgets to divide them into servings. Check the intermediate steps against a tiny case whose answer you already know.
Picture it like this
A debugger is like pausing a board game at a particular turn to see each player's cards and the moves that led there.
Where the picture stops working
Programs do not have players or physical cards. The analogy helps with pausing and inspecting state, but it does not explain Python syntax, tracebacks, or the rules of a stack frame.
Worked example
This example was executed with python3. The first call succeeds: 4 + 6 + 8 divided by 3 is 6.0. The second call exposes a reproducible runtime failure—an empty list makes the denominator zero. The traceback points to the division expression, but the diagnostic print also shows the state that led there. The focused correction is to decide and document how the function should handle an empty list; it is not to silently ignore every error.
def mean(values):
total = sum(values)
print(f"count={len(values)} total={total}")
return total / len(values)
print(mean([4, 6, 8]))
print(mean([]))
# count=3 total=18
# 6.0
# count=0 total=0
# ZeroDivisionError: division by zeroFor the empty case, the printed count=0 supports the hypothesis suggested by the final traceback line. After choosing a contract—for example, raising a clear ValueError before division—rerun both [4, 6, 8] and [] to confirm the change did not alter the ordinary case.
Key takeaway
Debug methodically: reproduce the behavior, use the error category or traceback to form one hypothesis, inspect the relevant state, make a focused change, and rerun the same case. A quiet program still needs its result checked.
Quick check
3 questions here, of 5 in this lesson’s practice set. Answers stay hidden until you check.
An unhandled traceback ends with KeyError: 'course'. What is the best first interpretation?
A function returns the sum of three scores when its stated job is to return their average. It runs without an exception. What kind of defect is this?
Study tools & related lessonsYou’ll learn to · Common mistakes · Easily confused · Key vocabulary · Related
You’ll learn to
- Distinguish syntax, runtime, and logic errors in Python.
- Read the essential parts of an unhandled-exception traceback.
- Apply a repeatable debugging loop to a small program.
- Use print-based inspection to check an intermediate value.
- Explain when a breakpoint debugger can provide more information than another print statement.
Common mistakes
Changing several lines before rerunning the failing case.
Make one focused change so the next run can provide evidence about that change.
Reading only the first line of a traceback.
Start with the final exception type and message, then trace upward to the relevant calls.
Assuming no crash means no defect.
Compare output with a known expected result to detect a logic error.
Leaving unlabelled debug prints throughout a program.
Use temporary, labeled observations and remove or replace them after the investigation.
Treating a broad exception handler as a debugging fix.
Diagnose the cause first; handle expected exceptions deliberately in the separate exception-design work.
Easily confused
Syntax error vs. Runtime exception
A syntax error prevents normal execution from beginning; a runtime exception occurs while valid code is executing.
Runtime exception vs. Logic error
An exception interrupts execution; a logic error can complete normally while producing an unintended result.
print-based inspection vs. pdb inspection
A print records a selected value when execution reaches it; pdb can pause, step, inspect frames, and evaluate expressions interactively.
Key vocabulary
- debugging
- A deliberate investigation of why a program's observed behavior differs from the intended behavior.
- syntax error
- A problem in code structure that Python detects while parsing the source before normal execution.
- runtime exception
- An event during execution that interrupts normal control flow unless code handles it.
- logic error
- A defect in which code runs but produces behavior or a result that violates its intended rule.
- traceback
- Python's report of the call sequence and source locations associated with an unhandled exception.
- reproduce
- Run a defined case again so an observed behavior can be compared before and after a change.
- breakpoint
- A chosen pause point where a debugger stops execution so program state can be inspected.
- stack frame
- The execution context for one active function call, including its local names.
Sources & references
- Errors and Exceptions — Python Documentation
- Errors and Exceptions — Python Documentation
- pdb — The Python Debugger — Python Software Foundation
- Think Python, 2nd Edition — Appendix A: Debugging — Allen B. Downey / Green Tea Press
EliExplains lessons are original prose written from the open, credible references above. See Copyright & Licensing.
Researched 2026-08-19
Educational content only. It is not medical, legal or professional advice. Found an error? Tell us.

