Python10 min readCode verified

Read the Traceback, and the Bug That Never Raises One

Three kinds of wrong, seven errors worth knowing by name, and the reading order that turns a wall of red text into one useful sentence. Every message on this page was produced by running the code.

Three kinds of wrong, and only two of them tell you#

Every bug in a first Python course is one of three kinds, and knowing which one you are looking at decides what you do next:

KindWhen it bitesWhat you see
Syntax errorbefore a single line runsone message, and no output at all
Runtime errorpart-way throughoutput up to that point, then a traceback
Logic errornevernothing. The program finishes and the answer is wrong

The third row is the one that costs marks. Python cannot know what you meant, so a program that runs cleanly and prints 56.67 where the answer is 80 looks exactly like a program that works.

Syntax errors point at the wrong line (on purpose)#

Three lines, one missing bracket:

name = "Ada"
print("Hello, " + name
print("Goodbye")
  File "hello.py", line 2
    print("Hello, " + name
         ^
SyntaxError: '(' was never closed

The mistake is that line 2 never closes its bracket, and Python says so precisely. What it cannot know is where you meant to close it — a bracket may legally span many lines, so it reads on, and on older Python versions the complaint surfaced against line 3 instead, which sends people hunting in the wrong place.

The habit worth building: when a syntax error names a line that looks fine, check the line above it. An unclosed bracket, quote, or a missing colon on the previous line accounts for most of them.

Two more that arrive constantly:

x = 5
if x = 5:
    print("five")
  File "cmp.py", line 2
    if x = 5:
       ^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

= assigns a value; == asks whether two values are equal. A condition needs the question, not the instruction. Modern Python guesses what you meant and says so in the message.

def f():
print("hi")
  File "ind.py", line 2
    print("hi")
    ^
IndentationError: expected an indented block after function definition on line 1

In Python, indentation is the block structure — there are no braces to disagree with it. A line that belongs inside a def, if, or for must be indented past it, and every line in the same block must be indented the same amount.

The named runtime errors#

These are worth knowing by name, because an exam will show you a broken line and ask which error it raises. Each message below was produced by running the code in the third column:

ErrorThe messageWhat causes it
NameErrorname 'total' is not definedusing a variable before assigning it — often a typo in the name
TypeErrorcan only concatenate str (not "int") to stran operation between two types that do not combine that way
ValueErrorinvalid literal for int() with base 10: '3.7'right type, impossible value — int("3.7"), int("abc")
ZeroDivisionErrordivision by zerodividing by a count that turned out to be 0
IndexErrorlist index out of rangea position past the end — a list of 3 has no index 3
KeyError'b'a dictionary key that is not there. Use .get() when it might be missing
AttributeError'int' object has no attribute 'upper'a method called on the wrong type — usually a number you thought was a string
TypeError vs ValueError, which people mix up

The distinction is whether the type is acceptable. int("abc") is a ValueError — a string is exactly what int() wants, this one just does not spell a number. int([1, 2]) is a TypeError — a list is not something it converts at all. Right kind of thing, wrong value: ValueError. Wrong kind of thing: TypeError.

Read a traceback from the bottom up#

A traceback looks like a wall of text. It is actually three pieces, and you read it backwards:

def average(values):
    return total(values) / len(values)

def total(values):
    s = 0
    for v in values:
        s = s + v
    return s

scores = []
print(average(scores))
Traceback (most recent call last):
  File "avg.py", line 11, in <module>
    print(average(scores))
          ^^^^^^^^^^^^^^^
  File "avg.py", line 2, in average
    return total(values) / len(values)
           ~~~~~~~~~~~~~~^~~~~~~~~~~~~
ZeroDivisionError: division by zero
  1. The last line is what went wrong. ZeroDivisionError: division by zero. Read it before anything else — everything above is just how you got there.
  2. The lowest File block is where it went wrong. Line 2, inside average. The ^ marks point at the exact operation: the division.
  3. The blocks above are the trail that led there, oldest first. Line 11 called average, which is where the empty list came from.

So: an empty list has length 0, and dividing by len(values) divides by zero. The fix is not on the line that crashed — it is deciding what an average of nothing should be, and saying so before you divide.

“most recent call last” is the instruction

That phrase at the top of every traceback is telling you the reading order. The deepest, most immediately relevant frame is at the bottom, next to the error message. Reading top-down sends you to the line that started the program rather than the line that broke.

The bug that raises nothing#

This program runs perfectly and prints a wrong answer:

scores = [90, 80, 70]
total = 0
for i in range(len(scores) - 1):
    total = total + scores[i]
print("Average:", total / len(scores))
Average: 56.666666666666664

No traceback, no warning, exit status fine. The average of 90, 80 and 70 is 80. The loop ran over range(2), so it added 90 and 80 and never reached 70, then divided that by 3.

The - 1 came from a real and reasonable fear — that range(len(scores)) would run one past the end. It does not. range(3) produces 0, 1, 2, which are exactly the valid positions of a three-item list. The guard against a bug you did not have created one you did.

What catches a logic error is arithmetic you can do in your head. Three scores, an obvious average of 80, and an answer of 56.67 — that gap is visible in a second, and it is invisible on a list of forty real numbers. Run every program once on data whose answer you already know.

Better still, drop the index entirely when you do not need it:

scores = [90, 80, 70]
total = 0
for s in scores:
    total = total + s
print("Average:", total / len(scores))
Average: 80.0

No range, no len, no index — and therefore no off-by-one to get wrong.

How to actually debug#

  1. Read the message literally, all of it. 'int' object has no attribute 'upper' already tells you the value is an int when you expected a str. Most first-course bugs are solved by believing the message instead of skimming it.
  2. Print the thing you are assuming. Not "it should be a number" — print(type(x), x) on the line before the crash. The answer is usually the assumption, not the logic.
  3. Halve the program. Comment out the second half. Still wrong? The bug is in the first half. Three or four halvings find almost anything, and it beats rereading from the top.
  4. Trace by hand with a table. One column per variable, one row per pass through the loop. Write the numbers down — doing it in your head is where the mistake hides, and it is the exact skill a trace-the-output exam question tests.
  5. Explain the line out loud. The sentence usually stops half-way, at the word you were unsure of. That word is the bug.

Where these go wrong#

  • Hunting on the line the syntax error names. Check the line above first — unclosed bracket, unclosed quote, missing colon.
  • Reading the traceback top-down. The error is the last line; the place is the lowest File block.
  • Writing range(len(xs) - 1) "to be safe." range(len(xs)) is already exactly the valid positions. The - 1 silently drops the final item.
  • Confusing ValueError with TypeError. Wrong value of the right type is ValueError; wrong type altogether is TypeError.
  • Assuming no traceback means no bug. Logic errors never raise anything. Test on data whose answer you already know.
  • Fixing the crashing line. The traceback shows where it surfaced. The cause is often the line that supplied the value — an empty list, a string that should have been converted.
  • Deleting code until the error stops. That removes the symptom. Halve, find, understand, then fix.

Test yourself in the free Kestrel Exams app

Topic-selectable practice — offline, no ads, no account.

Practice this topic →

Frequently asked questions#

Why does my Python syntax error point at the wrong line?

Because brackets and quotes may legally span several lines, so when one is left open Python keeps reading and only complains when the following line cannot make sense. Modern Python reports '(' was never closed against the opening line, but the habit still applies: when a syntax error names a line that looks fine, check the line above it.

What is the difference between a TypeError and a ValueError?

A TypeError means the kind of thing is wrong — int([1, 2]) cannot work because a list is not something int() converts. A ValueError means the kind of thing is right but the particular value is impossible — int("abc") is a string, exactly what int() takes, it just does not spell a number.

How do you read a Python traceback?

Bottom-up. The last line names the error and the reason. The lowest File block is where it happened, with ^ marks under the exact operation. The blocks above are the chain of calls that led there, oldest first — which is what the header “most recent call last” is telling you.

What are the four named errors a first Python exam asks about?

Syntax errors (the program never runs), NameError (a variable used before it was assigned), TypeError (an operation between types that do not combine) and ValueError (the right type holding an impossible value). ZeroDivisionError, IndexError and KeyError come up nearly as often.

Why does my program run fine but give the wrong answer?

That is a logic error, and Python raises nothing for it because the code is valid — it just does not do what you meant. The classic is range(len(xs) - 1), which silently skips the last item. Catch these by running on data whose answer you already know.

Does range(len(my_list)) go past the end of the list?

No. range(3) produces 0, 1, 2 — exactly the valid positions of a three-item list. Subtracting one “to be safe” drops the final item. If you do not need the index, loop over the list directly with for item in my_list.

Suggest a change

Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.