Python Programming · Foundations

Debugging

Want it in plain words first? Jump to Eli explains — the same idea, no jargon.
On this page 9 sections
  1. In 30 seconds
  2. Why this matters
  3. The college version
  4. Eli explains
  5. Worked example
  6. Key takeaway
  7. Quick check
  8. Study tools
  9. Sources & references

In 30 seconds

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 for an unhandled . A program can also run without crashing yet calculate the wrong result; that is a , 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 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: 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 , 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, the EliExplains learning guide

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 zero

For 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.

Question 1 of 3foundational

Which situation is a Python syntax error?

Choose an answer, then check it.
Question 2 of 3intermediate

An unhandled traceback ends with KeyError: 'course'. What is the best first interpretation?

Choose an answer, then check it.
Question 3 of 3intermediate

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?

Choose an answer, then check it.
Practice all 5

Keep learning

Ready to build on this? Continue to the next lesson.

Practice this lesson
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

  1. Errors and Exceptions — Python Documentation
  2. Errors and Exceptions — Python Documentation
  3. pdb — The Python Debugger — Python Software Foundation
  4. 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.