WasiLearn Code
Python · When things go wrong

Finding a bug without guessing

The slow way to debug is to stare at the code until inspiration arrives. The fast way is to find out what the program is actually doing, which is almost never what you assumed.

prices = ["250", "120"]
total = 0

for price in prices:
    total = total + int(price)

print(total)
output
370

That works. When it does not, the question is never "what did I write?" — it is "what value was in that variable at that moment?"

Read the traceback from the bottom

Every Python crash prints a traceback, and every beginner reads it top-down and gives up. Read the last line first.

Traceback (most recent call last):
  File "shop.py", line 22, in <module>
    print(receipt(order))
  File "shop.py", line 15, in receipt
    return format_total(total(order))
  File "shop.py", line 9, in total
    return sum(int(p) for p in prices)
ValueError: invalid literal for int() with base 10: 'free'

The last line is what went wrong: a ValueError, and — crucially — the exact offending value, 'free'.

The line above it is where: shop.py line 9, inside total.

The lines above that are how you got there, most recent last. That is what "most recent call last" at the top means: the bottom of the stack is the newest frame, which is why reading upwards traces the path back to your own code.

Two lines of a traceback usually answer the whole question. The rest is context for when they do not.

print, and why repr is better

print is not a beginner's tool that real programmers outgrow. It is the fastest way to see a value, and it is what most debugging is.

The refinement worth learning is repr:

value = "250 "

print(value)
print(repr(value))
print(len(value))
output
250 
'250 '
4

print shows what the value looks like. repr shows what it is, quotes and all. A trailing space, a newline, a number that is secretly a string — all invisible with print, all obvious with repr.

f-strings have a shorthand, !r:

name = "  Ayesha\n"
print(f"name={name!r}")
output
name='  Ayesha\n'

Print the name alongside the value, or you will lose track of which line produced what:

subtotal = 250
tax = 37.5

print(f"{subtotal=} {tax=}")
output
subtotal=250 tax=37.5

The = inside the braces prints the expression and its value. It is built for exactly this and saves typing the name twice.

Print the type when the value looks right

quantity = "3"

print(quantity)
print(f"{quantity!r} {type(quantity).__name__}")
output
3
'3' str

The first line looks perfectly correct and is the cause of the bug. Any time a value prints as expected but behaves oddly, print its type — it is a string where a number should be more often than any other single cause.

Bisect the problem

When you do not know where a bug is, do not read the whole program. Cut it in half.

def total(prices):
    cleaned = [p.strip() for p in prices]
    numbers = [int(p) for p in cleaned]
    return sum(numbers)

prices = [" 250", "120 "]

cleaned = [p.strip() for p in prices]
print(f"after strip: {cleaned!r}")

numbers = [int(p) for p in cleaned]
print(f"after int:   {numbers!r}")

print(total(prices))
output
after strip: ['250', '120']
after int:   [250, 120]
370

Put a print halfway through. If the value is right there, the bug is after it; if wrong, before. Repeat, and each check halves what is left. Ten lines takes three or four checks, a thousand takes ten.

This beats reading, because reading is where the wrong assumption came from in the first place.

Check your assumption, not your code

Most bugs are a belief that does not match reality. So test the belief directly.

stock = {"chai": 12, "Coffee": 4}
wanted = "coffee"

print(wanted in stock)
print(list(stock.keys()))
print(wanted.lower() in [k.lower() for k in stock])
output
False
['chai', 'Coffee']
True

"The key is in the dictionary" was the assumption. One line disproved it, and the capital C is the entire bug. No amount of staring at the lookup code would have shown that, because the lookup code is correct.

breakpoint()

When printing is not enough, breakpoint() stops the program and gives you a prompt where you can inspect anything.

def total(prices):
    breakpoint()
    return sum(int(p) for p in prices)

Run it from a terminal and you land in pdb. The commands worth memorising:

Command Does
p name print a variable
pp name pretty-print it
n next line, stepping over calls
s step into a call
c continue to the next breakpoint
l list the code around here
w show the call stack
q quit

You can type any Python expression at the prompt, which is the real value — you are inside the program with all its variables live, rather than guessing from outside.

breakpoint() needs a terminal, so it does not work in the runnable blocks on this page. Save a .py file and run it with python filename.py to try it.

Rubber-ducking

Explain the code out loud, line by line, to somebody who is not listening — a colleague, a rubber duck, an empty room. It works because speaking forces you to say what each line does rather than skimming what you meant, and the gap between the two is usually where the bug is.

More often than is comfortable, you find it mid-sentence.

Change one thing at a time

When a fix does not work, undo it before trying the next one. Three simultaneous changes that together fix the problem leave you not knowing which one mattered, and two of them are probably new bugs waiting.

Keep the program running between changes, too. A version that runs and gives a wrong answer is far more informative than one that will not start.

What to reach for, in order

  1. Read the last line of the traceback. It usually names the value.
  2. Print the suspect value with repr and its type. Half of all bugs stop here.
  3. Bisect — print halfway, then halve again.
  4. Test the assumption directly rather than the code around it.
  5. breakpoint() when you need to look at everything at once.

And when you have found it, consider whether a check at the boundary would have caught it earlier — that is what raising errors is for, and reading errors covers the messages Python gives you along the way.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes