Phase 1 · Programming Fundamentals
Debugging & Problem-Solving
Read tracebacks, isolate bugs with prints and checks, and break problems into clear steps.
On this page
Reading a traceback
When Python crashes, it prints a traceback: the call stack from the failure up to your script.
Read from the bottom: the last line names the exception and message. The lines above show where it happened.
def area(width, height):
return width * height
# Uncommenting the next line raises TypeError:
# print(area("3", 4))
# Traceback points to the multiply line:
# TypeError: can't multiply sequence by non-int of type 'float'
# (or similar) — meaning the types were wrong.# SyntaxError — code cannot even parse (missing :, quotes)
# NameError — variable/function name not defined
# TypeError — wrong type for an operation
# ValueError — right type, wrong value (int("abc"))
# IndexError — list index out of range
# KeyError — dict key missing
# ZeroDivisionError — division by 0
print("Learn these names—they speed up debugging.")Fix the first real error first. Later errors often disappear once the earlier one is resolved.
Print debugging (still useful)
Before fancy debuggers, print what you have: inputs, types, and intermediate values.
Print labels with values so logs are readable: print("n=", n, "type=", type(n)).
def normalize(scores):
print("raw:", scores)
total = sum(scores)
print("total:", total)
if total == 0:
return scores
return [s / total for s in scores]
print(normalize([2, 3, 5]))Remove or silence debug prints before you commit—noise hides real signal.
Assert, reproduce, shrink
assert condition, "message" documents expectations and fails fast when they break.
Reproduce the bug with the smallest input that still fails. Smaller bugs are easier to reason about.
def discount(price, percent):
assert price >= 0, "price must be >= 0"
assert 0 <= percent <= 100, "percent out of range"
return price * (1 - percent / 100)
print(discount(100, 10))
# discount(-5, 10) # AssertionError# Bug report: "average crashes on empty list"
# Minimal reproduce:
scores = []
try:
avg = sum(scores) / len(scores)
except ZeroDivisionError:
avg = 0
print(avg)A simple problem-solving loop
1. Restate the goal in one sentence.
2. Write examples: input → expected output (including edge cases).
3. Solve the happy path with functions.
4. Add validation and error handling.
5. Test edge cases: empty, zero, huge, weird types.
# Goal: clamp a number into [low, high]
# Examples:
# clamp(5, 0, 10) -> 5
# clamp(-1, 0, 10) -> 0
# clamp(99, 0, 10) -> 10
def clamp(n, low, high):
if n < low:
return low
if n > high:
return high
return n
tests = [(5, 0, 10, 5), (-1, 0, 10, 0), (99, 0, 10, 10)]
for n, lo, hi, expected in tests:
got = clamp(n, lo, hi)
print(n, "->", got, "OK" if got == expected else "FAIL")Rubber-duck it: explain the bug out loud (or in a comment). Gaps in the story often reveal the mistake.
Practice
Mini challenge: safe divide
Write safe_divide(a, b) that returns a/b, or None if b is 0. Include two asserts for numeric-ish use (optional), then run a tiny test list of (a, b, expected) cases and print OK/FAIL.
def safe_divide(a, b):
# TODO: return a/b or None when b == 0
pass
cases = [(10, 2, 5.0), (5, 0, None), (9, 3, 3.0)]
for a, b, expected in cases:
got = safe_divide(a, b)
print(a, b, "->", got, "OK" if got == expected else "FAIL")
Interactive
Try It in the browser
Powered by Pyodide
Ask for two numbers as text. Try to divide float(a)/float(b). Catch ValueError and ZeroDivisionError separately and print a clear message for each.
Output
—
Errors
—
Check understanding
Lesson quiz
Score 4/5 or higher to mark this day complete.