How to understand error messages in Python

Big Idea

When Python encounters a problem it cannot continue past, it stops and prints an error message. That message is not a punishment — it is a precise report telling you exactly what went wrong, on exactly which line, and often why. Learning to read error messages is one of the most valuable debugging skills you can develop. A programmer who reads the error carefully fixes the bug faster than one who stares at the code and guesses.

This article is the starting point for the Debugging section of the knowledge base. It focuses on one skill: reading what Python tells you when it stops. Later articles in this section cover the three types of error in more depth, strategies for finding bugs that do not produce error messages, and how to use print() statements as a debugging tool.

LevelWhat it coversWhen to read it
Basic ideaThe structure of a Python error message; the four things to read and in which orderRight now.
At a deeper levelThe most common error types and what each one means; how to read a traceback with multiple framesOnce you have seen a few error messages in your own programs.
At the deepest levelHow Python produces error messages; what the traceback reveals about the call stackWhen you are comfortable reading errors and want to understand what is happening underneath.

1   The Structure of a Python Error Message

Basic Idea

Every Python error message has the same structure. Once you know it, any error — no matter how alarming it looks — can be read in a few seconds.

Here is a complete error message:

Traceback (most recent call last):
  File "main.py", line 5, in <module>
    total = total + score
NameError: name 'score' is not defined

Read it in this order:

  1. The last line. This is the error type and the message. It is the most important part. Read it first.
  2. The line number. The number after line tells you where Python was when the error occurred.
  3. The code snippet. The line of your code that was running at the moment of the error.
  4. The file name. Usually you only have one file, but when you have multiple files this tells you which one.

In the example above: the error is a NameError, meaning Python encountered a name it did not recognise. The message says 'score' is not defined. This happened on line 5 of main.py. The line of code was total = total + score. Now you know exactly where to look and what to look for: something is wrong with the variable score on or before line 5.

At a Deeper Level

The word "Traceback" at the top is easy to ignore — do not. It announces that what follows is a record of where Python was when the error happened. The phrase "most recent call last" tells you the order: the first entry in the list is the outermost call (usually your main program), and the last entry is the innermost call (the exact line that crashed).

For a simple program with no functions, there is only one entry in the traceback and the structure is straightforward. When functions are involved, there can be several entries. The rule is always the same: read the last entry first, because that is the line that actually crashed.

Read bottom to top, not top to bottom

The natural instinct is to read the error message from the top. Resist this. The most useful information — the error type, the message, and the exact crashing line — is at the bottom. Start there, understand what went wrong, then work upward through the traceback only if you need to understand how the program got to that point.

At the Deepest Level

Python generates error messages by catching exceptions — objects that represent error conditions — and printing their type, message, and the current state of the call stack. The call stack at the moment of the error is what the traceback shows: each entry is one frame on the stack, from the outermost (the script entry point) to the innermost (the crashing line). The frames are printed in order from oldest to most recent, which is why "most recent call last" means the bottom entry is the crash site.

Understanding this also explains why the line number is sometimes one line before the actual mistake. Python reports the line where it detected the problem, not always the line where the mistake was made. A missing closing parenthesis on line 4 might only be detected on line 5 when Python tries to parse the next statement and finds something unexpected. The line number is where to start looking, not necessarily where to stop.

2   The Three Types of Error

Basic Idea

Python errors fall into three categories. Knowing which category you are dealing with tells you where to look and what kind of fix to expect.

TypeWhen it happensWhat it meansExample
Syntax errorBefore the program runs at allThe code is not valid Python — a missing colon, unclosed bracket, or misspelled keywordForgetting the : at the end of an if statement
Runtime errorWhile the program is runningSomething went wrong during execution — dividing by zero, accessing a missing key, calling a method on the wrong typeTrying to use a variable that was never assigned a value
Logic errorThe program runs without crashing but produces wrong resultsThe code is valid Python and runs successfully, but it does not do what you intendedUsing < instead of <= in a condition

Syntax errors and runtime errors both produce error messages. Logic errors produce no error message at all — the program simply gives wrong output. This article focuses on syntax and runtime errors. Logic errors are covered in the Finding Logic Errors article.

At a Deeper Level

The distinction between syntax and runtime errors matters because they require different responses.

A syntax error means the program could not even start. Python reads through your file before running anything, checking that the code is valid Python syntax. If it finds a problem, it stops immediately and reports the location. Nothing in your program has run yet. The fix is always structural: a missing or extra character, a misspelled keyword, an unclosed bracket.

A runtime error means the program started and ran successfully up to a point, then encountered a situation it could not handle. This is more nuanced: some of your code ran correctly before the crash. The line reported in the error is where execution stopped — but the cause might be earlier, where a variable was given a bad value or a list was built incorrectly.

Syntax errors: look at the reported line and the line before

Python's syntax error messages are sometimes off by one line — the error is detected when Python reaches the next line and cannot make sense of what it finds. If you look at the reported line and see nothing wrong, check the line immediately above it. A missing colon, unclosed parenthesis, or unclosed string on the line above is the most common cause.

At the Deepest Level

Python's execution model has two distinct phases. In the parse phase, Python reads the source code and converts it to bytecode — a lower-level set of instructions. Syntax errors are detected here. In the execution phase, Python runs the bytecode. Runtime errors are raised here, when an instruction encounters an invalid state.

This is why a syntax error can appear in a function body that is never called: the entire file is parsed before any of it runs. Conversely, a runtime error inside a function that is never called in a particular run will never appear — the problematic bytecode exists but is never executed. A program can contain code paths with runtime errors that never surface during testing if those paths are never reached.

3   The Most Common Error Types

Basic Idea

Python's error types are specific — each name tells you something about what went wrong. You do not need to memorise all of them. You need to recognise the ones you will encounter most often.

Error typeWhat it meansMost common cause
SyntaxErrorThe code is not valid Python syntaxMissing colon after if, for, def, or while; unclosed bracket or string; misspelled keyword
IndentationErrorThe indentation is inconsistent or missingForgetting to indent after a colon; mixing tabs and spaces; incorrect dedent
NameErrorA variable or function name is used before it is definedTypo in a variable name; using a variable before assigning it; calling a function that does not exist yet
TypeErrorAn operation is applied to a value of the wrong typeAdding a string and an integer; calling a method on None; passing the wrong number of arguments to a function
ValueErrorAn operation receives the right type but an invalid valuePassing a non-numeric string to int(); calling list.remove() with a value that is not in the list
IndexErrorA list index is outside the valid rangeAccessing list[5] when the list has fewer than 6 items; off-by-one in a loop
KeyErrorA dictionary key does not existReading from a dictionary with a key that was never added; typo in a key string
ZeroDivisionErrorDivision or modulo by zeroA denominator variable that was set to zero or never set; user input of zero not validated
AttributeErrorAn object does not have the attribute or method being accessedCalling a list method on a string; calling a string method on an integer; using a variable that contains None as if it were an object
At a Deeper Level

Here is each error type with a concrete example — the code that causes it, the message Python produces, and the diagnosis.

SyntaxError

if x > 5
    print("big")
  File "main.py", line 1
    if x > 5
            ^
SyntaxError: expected ':'

Diagnosis: the colon is missing at the end of the if statement. Python even tells you what it expected.

NameError

print(mesage)
NameError: name 'mesage' is not defined

Diagnosis: mesage is a typo — the variable is probably called message. The name has never been assigned.

TypeError

age = input("Enter age: ")
next_year = age + 1
TypeError: can only concatenate str (not "int") to str

Diagnosis: input() always returns a string. Adding the integer 1 to a string is not valid. Fix: age = int(input("Enter age: ")).

IndexError

items = ["a", "b", "c"]
print(items[3])
IndexError: list index out of range

Diagnosis: the list has three items at indexes 0, 1, and 2. Index 3 does not exist.

KeyError

student = {"name": "Alex", "grade": 9}
print(student["age"])
KeyError: 'age'

Diagnosis: the key "age" was never added to the dictionary. The message tells you exactly which key was missing.

ZeroDivisionError

total = 100
count = 0
average = total / count
ZeroDivisionError: division by zero

Diagnosis: count is 0. Division by zero is undefined. Fix: check that count is not zero before dividing.

AttributeError

def get_name():
    print("Alex")

result = get_name()
print(result.upper())
AttributeError: 'NoneType' object has no attribute 'upper'

Diagnosis: get_name() prints instead of returning, so result is None. None does not have an upper() method. The message 'NoneType' object has no attribute is almost always a sign that a function returned None when it should have returned a value.

'NoneType' object has no attribute is a very common message

When you see AttributeError: 'NoneType' object has no attribute '...', your first question should be: where did None come from? Look at the variable being used on the crashing line and trace backward to where it was assigned. Almost always, it came from a function that forgot to return a value, or from a method that modifies in place and returns None (like list.sort()).

At the Deepest Level

All of these error types are Python exceptions — classes that inherit from a base class called Exception. When Python encounters an error condition, it creates an instance of the appropriate exception class (for example, NameError("name 'score' is not defined")) and raises it. If nothing catches it, Python's default handler prints the traceback and terminates the program.

The exception hierarchy is one reason why error messages are specific. ZeroDivisionError is a subclass of ArithmeticError, which is a subclass of Exception. Python raises the most specific applicable class, which is why the message says ZeroDivisionError rather than just ArithmeticError. More specific messages give you more information.

In later courses you will learn to catch exceptions deliberately using try/except — this allows you to handle error conditions in your program rather than letting them crash it. For Grade 9, the focus is on reading exceptions rather than catching them.

4   Reading a Multi-Frame Traceback

Basic Idea

When a program has functions, a crash can happen deep inside a chain of function calls. The traceback shows every step of that chain — every function that was active when the crash occurred — so you can see how the program got there.

The reading rule is still the same: start at the bottom. The bottom entry is the line that actually crashed. The entries above it show the path that led there.

At a Deeper Level

Here is a program with three functions:

def calculate_average(scores):
    return sum(scores) / len(scores)

def process(data):
    result = calculate_average(data)
    return result

def run():
    numbers = []
    print(process(numbers))

run()

Running this produces:

Traceback (most recent call last):
  File "main.py", line 11, in <module>
    run()
  File "main.py", line 9, in run
    print(process(numbers))
  File "main.py", line 5, in process
    result = calculate_average(data)
  File "main.py", line 2, in calculate_average
    return sum(scores) / len(scores)
ZeroDivisionError: division by zero

Reading from the bottom:

  1. The error: ZeroDivisionError: division by zero. Division by zero happened.
  2. The crash site: line 2, inside calculate_average, on the line return sum(scores) / len(scores). The list must be empty, making len(scores) equal to zero.
  3. How we got there: reading upward — calculate_average was called from line 5 inside process, which was called from line 9 inside run, which was called from line 11 in the main program.

The diagnosis: numbers was created as an empty list on line 8 and passed into process and then into calculate_average. Dividing by len([]) — which is 0 — caused the crash. The fix is to check that the list is not empty before calculating the average.

The crash site and the cause are often in different places

The bottom of the traceback shows where Python stopped. The cause of the problem is often higher up — where a bad value was created or a wrong decision was made. In this example, the crash is in calculate_average but the root cause is in run, where an empty list was passed in without checking. Read the crash site to understand what went wrong. Read upward to understand why.

At the Deepest Level

Each entry in the traceback corresponds to one frame on the call stack at the moment of the crash. The traceback is a snapshot of the stack printed from bottom to top in terms of depth — the outermost frame first (line 11 in <module>), the innermost last (line 2 in calculate_average).

The <module> label in the first frame means the code at module level — the lines of your script that are not inside any function. This is the entry point of the program.

Professional debugging tools like pdb (Python's built-in debugger) and VS Code's graphical debugger allow you to inspect the call stack interactively at the moment of a crash — not just read a printed snapshot. You can move between frames, inspect variable values in each frame, and step forward or backward through execution. These tools make the same information that the traceback summarises available in an explorable form.

5   A Process for Responding to an Error Message

Basic Idea

When you see an error message, do not immediately change any code. Read the message first. The order matters: read, diagnose, fix — not fix, then read.

  1. Read the last line. What is the error type? What does the message say?
  2. Find the line number. Go to that line in your code.
  3. Read the code snippet shown in the traceback. Does it match what you expect that line to say?
  4. Diagnose before touching anything. Write one sentence describing what you think the problem is. "I think this is failing because score was never assigned a value." If you cannot write that sentence, you do not understand the error yet — go back to step 1.
  5. Make one change to address your diagnosis. Run the program again.
  6. If the same error appears, your diagnosis was wrong. Read the message again — it may have changed slightly, which is new information.
At a Deeper Level

Step 4 — writing a diagnosis before touching code — is the step most beginners skip. It feels slower. It is actually faster, because it prevents the most common debugging trap: making random changes until something stops crashing.

Random changes are dangerous for two reasons. First, they can mask the original error without fixing it — the program stops crashing for a different reason, and the underlying bug is still there. Second, they introduce new code that you did not fully understand, which can cause new bugs.

A written diagnosis forces you to commit to a theory. If your fix works, your theory was right and you have learned something. If it does not, you know your theory was wrong and you can form a different one. Either way you are learning.

Copy the exact error message before searching for help

When you ask a classmate, a teacher, or an AI for help with an error, copy the complete error message — all of it, including the traceback. "I got a NameError" tells someone very little. The full message, including the file, line number, code snippet, and error text, gives them everything they need to understand the problem. The same applies when asking in Mode 2: paste the full error, describe what you already tried, and describe what you think the problem might be. That context is what separates a useful AI response from a generic one.

At the Deepest Level

The process above is a simplified version of what experienced programmers call the scientific method of debugging, described in detail by David Agans in his book Debugging. The steps are: understand the system, make the bug reproducible, see it fail, quit thinking and look, divide and narrow the problem, change one thing at a time, keep a record. The common thread is that debugging is an empirical process — you form hypotheses and test them — not a process of intuition and random edits.

For beginners, "quit thinking and look" is particularly important. The error message is evidence. Your mental model of what the program should be doing is a hypothesis. When the evidence and the hypothesis conflict, trust the evidence. The program does what you wrote, not what you meant.

6   What Python Cannot Tell You

Basic Idea

Error messages are powerful, but they have limits. Python can only tell you where execution stopped and what kind of problem it encountered. It cannot tell you why you made the mistake, whether your logic is correct, or whether your program does what you intended.

A program with no error messages can still be completely wrong. If your loop runs one time too few, or your condition uses < instead of <=, or your function returns the wrong value in one specific case — Python will not report any of these. They are logic errors, and they are invisible to the error message system.

Error messages tell you about crashes. Logic errors require a different set of tools: trace tables, test cases, and careful reading. Those are covered in other articles in this section.

At a Deeper Level

There is also a class of errors that Python reports on the wrong line. Consider:

result = (
    first_value +
    second_value
    third_value
)
  File "main.py", line 4, in <module>
    third_value
SyntaxError: invalid syntax

Python reports the error on line 4, where third_value appears. But the actual mistake is on line 3: a missing + between second_value and third_value. The reported line is where Python noticed the problem, not where the problem began. When a syntax error seems to point at a line that looks completely fine, check the line above it.

Check Your Understanding
  1. A Python error message always ends with the same two-part structure. What are the two parts, and which one should you read first?
  2. Name the three types of error in Python. For each one, describe when it is detected and give one example of what causes it.
  3. Read this error message and answer the questions below.

    Traceback (most recent call last):
      File "grades.py", line 12, in <module>
        print(average(scores))
      File "grades.py", line 4, in average
        return total / count
    TypeError: unsupported operand type(s) for /: 'str' and 'int'
    • What type of error is this?
    • On which line did the program crash?
    • What was the program trying to do on that line?
    • What does the error message tell you about the values involved?
    • Write a one-sentence diagnosis: what do you think the cause is?
  4. A student gets a NameError: name 'Score' is not defined but insists they definitely defined score earlier in the program. What is the most likely cause, and what should they check?
  5. You see this message: AttributeError: 'NoneType' object has no attribute 'append'. Without seeing the code, describe the two most likely causes and how you would investigate each one.
  6. Explain in your own words why you should write a diagnosis before making any change to your code. What goes wrong when you skip this step?
  7. A student's program runs without any error messages but prints the wrong answer. Which type of error is this? Can Python's error message system help find it? What should the student do instead?