Debugging Basics
Find and fix what's going wrong
In this lesson
Debugging is the process of finding and fixing the bugs (errors) in your code. Every programmer writes bugs — the skill isn’t avoiding them, it’s fixing them efficiently — and debugging is a process you can learn that turns frustrating stuck moments into routine puzzles.
Explain it like I’m 5
Debugging is detective work. The program is doing something you didn’t expect, and your job is to gather clues until you find exactly where reality and your expectation part ways.
Read the traceback — it's pointing at the problem
When Python hits an error, it prints a traceback. Read it from the bottom up: the last line names the error type and message, and just above it is the file and line number where it happened. The traceback is not noise — it’s Python telling you precisely where and why it stopped.
def average(numbers):
return sum(numbers) / len(numbers)
scores = []
print(average(scores))
ZeroDivisionError: division by zero
Print what you think is true
The humblest, most effective tool is print(). When a value isn’t what you expect, print it: print("x is", x). Bugs hide in the gap between what you assume a variable holds and what it actually holds. Printing closes that gap and turns guesses into facts.
price = "10"
quantity = 3
# print(price * quantity) would give "101010" — a clue!
print("price is", price, "type:", type(price))
total = int(price) * quantity
print("total:", total)
price is 10 type: <class 'str'> total: 30
This should print the average but it crashes. Read the error in your head, then fix it so it prints 20.0.
def average(numbers):
return sum(numbers) / len(numbers)
result = average([10, 20, 30])
print("Average:" + result)
You can't join text and a number with +. Use a comma in print, or convert with str(result).
def average(numbers):
return sum(numbers) / len(numbers)
result = average([10, 20, 30])
print("Average:", result)
Average: 20.0
Isolate the problem
Narrow down where things go wrong. Comment out sections, or add prints at a few points, to find the last spot where everything was still correct. Once you know the failing change is between line A and line B, the bug has nowhere left to hide. Fix one thing at a time and re-run.
Check your assumptions and explain it aloud
Most bugs come from a wrong assumption: a variable is a string when you thought it was a number, a list is empty, a condition is backwards. ‘Rubber-duck debugging’ — explaining the code line by line to a rubber duck (or anyone) — works because saying it out loud forces you to check each step instead of skimming past the wrong one.
Two more tools: assert and breakpoint()
Beyond print(), two built-ins help you catch and chase down bugs. assert condition states something you believe is true; if it isn’t, Python stops immediately with an AssertionError, catching a broken assumption right where it happens instead of letting it cause weird behaviour later.
breakpoint() is more powerful: placed on a line, it drops you into Python’s interactive debugger when execution reaches it, so you can inspect variables and step through the code. Start with prints; reach for these as your programs grow.
def average(numbers):
assert len(numbers) > 0, "numbers must not be empty"
return sum(numbers) / len(numbers)
print(average([10, 20, 30]))
print(average([]))
20.0 AssertionError: numbers must not be empty
Common mistake: Panicking at a wall of traceback text
Tracebacks look intimidating, so beginners skip them — but they contain the exact answer to where and why the code failed.
Read the last line first (the error type and message), then the line number just above it. That’s usually all you need to locate the bug.
Common mistake: Changing code randomly hoping it works
Without understanding the cause, it’s tempting to shuffle things until the error disappears — which often hides the bug instead of fixing it.
Form a hypothesis, test it with a print(), then make one deliberate change. Understand why the fix works.
Where is the actual error message in a traceback?
Read tracebacks bottom-up: the final line names the error; the lines above trace how the code got there.
What's a quick way to check what a variable actually holds?
Printing the value — and type() — turns assumptions into facts and reveals most bugs.
Mini exercise (medium)
This code should greet each name but raises a TypeError: names = "Ada" then for n in names: print("Hi", n). Diagnose what names really is and fix it so it greets Ada, Bo, and Cy.
Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.
names = "Ada" # BUG: looping a string visits its letters
greetings = []
for n in names:
greetings.append("Hi " + n)
print(greetings)
Looping over a string visits each character. names should be a list of names.
names = ["Ada", "Bo", "Cy"]
greetings = []
for n in names:
greetings.append("Hi " + n)
print(greetings)
assert greetings == ["Hi Ada", "Hi Bo", "Hi Cy"], "names should be a list of names"
print("✓ Fixed!")