Create your own
Lesson illustration

Debugging Python Errors with Tracebacks

Hi. In the previous lesson, you used loops and list comprehensions to transform collections, and you checked a small transformation with assert. That is a useful starting point for debugging: code often fails inside a function, loop, or comprehension, but Python gives you a structured report rather than leaving you to guess.

Today you will learn the 80/20 debugging skill that pays off throughout ML work: read a Python traceback, find the relevant line, make an intentional correction, and verify the program now does what you meant. This is not just about removing red error text. A program can run and still compute the wrong metric or transform data incorrectly, so verification matters.


A traceback is Python’s failure report

When an unhandled error stops a program, Python prints a traceback. Think of it as a compact record of:

  1. What kind of problem occurred
  2. What Python was trying to do
  3. Where in the code it happened
  4. How execution reached that point

The most useful rule is:

Read a traceback from the bottom upward.

The final line usually tells you the exception type and Python’s explanation. Then move upward to locate the code line that triggered it.

A Python traceback with its key parts labelled: the file path, line number, function or module, failing line of code, exception type, and explanatory message. Read the stack frames from the bottom upward, beginning with the final `NameError` message.

Here is the same idea in a compact reference table:

Traceback partWhat it tells youFirst action
Final line, such as NameError: ...The error category and usually a clue about why it occurredRead this first
File "...", line 12, in function_nameThe file, line number, and current functionOpen that location
Code line below the file locationThe statement Python was executingCompare it with your intent
Earlier file-and-line entriesThe chain of calls that led to the failureCheck these if the immediate line is not the true source

A traceback is useful evidence, not a complete diagnosis. For example, if Python says a variable is a string where a number was expected, the failing addition may be perfectly reasonable. The real correction may be at the earlier point where the value entered your program.

Traceback (most recent call last): Python's tracebacks explained

Watch “Traceback (most recent call last): Python's tracebacks explained” by Python Morsels for a short visual explanation of traceback reading and call-stack frames.

Watch the walkthrough. Focus on the bottom-up reading order, then notice the distinction between the line where Python fails and an earlier caller where an unsuitable value may have been supplied.

Understanding the Python Traceback – Real Python

Read the traceback overview from Real Python to reinforce the visual labels and bottom-up method.

In “How Do You Read a Python Traceback?”, under “Python Traceback Overview,” read the traceback map. Focus on the final error line, the two-line call entries above it, and why the lowest call entry is usually closest to the actual failure.


The debugging loop: locate, explain, correct, verify

When an error appears, avoid randomly changing several lines at once. That can make the original issue harder to understand and can introduce a new one.

Use this repeatable five-step loop:

  1. Run the code and preserve the traceback. Read the full output rather than only the first line you notice.
  2. Start at the bottom. Identify the exception type and its message.
  3. Find the closest relevant code frame above it. Go to its file and line number.
  4. State the mismatch in plain language. For example: “I defined score_list, but the function refers to scores.”
  5. Make the smallest correction consistent with the intended behavior, rerun, and check an expected result.

The fourth step is particularly important. If you can explain the mismatch before editing, you are debugging rather than guessing.

Worked example: a NameError

Suppose you write a helper function to summarize episode rewards:

def mean_reward(reward_list):
    return sum(rewards) / len(rewards)

episode_rewards = [2, 4, 6]
print(mean_reward(episode_rewards))

When you run it, Python produces a traceback similar to this:

Traceback (most recent call last):
  File "agent_scores.py", line 5, in <module>
    print(mean_reward(episode_rewards))
  File "agent_scores.py", line 2, in mean_reward
    return sum(rewards) / len(rewards)
NameError: name 'rewards' is not defined

Read it upward.

1. Read the final line

NameError: name 'rewards' is not defined

A NameError means Python cannot find a name that your code refers to. Here, the missing name is rewards.

2. Find the closest frame

The frame immediately above the final line says:

File "agent_scores.py", line 2, in mean_reward
    return sum(rewards) / len(rewards)

Python failed at line 2, inside mean_reward().

3. Compare that line with the function definition

The function parameter is named reward_list:

def mean_reward(reward_list):

But the return statement uses rewards:

return sum(rewards) / len(rewards)

That is a naming mismatch. rewards was never defined inside the function.

4. Correct the code deliberately

Use the actual parameter name consistently:

def mean_reward(reward_list):
    return sum(reward_list) / len(reward_list)

episode_rewards = [2, 4, 6]
print(mean_reward(episode_rewards))

Expected output:

4.0

The top frame in the traceback, line 5, is still useful: it shows that mean_reward() was called from the main program. But the lower frame tells you the immediate failure occurred inside the function.


Common error messages: translate them, do not memorize them

You do not need to memorize every Python exception. Learn to translate a few frequent ones into questions you can investigate.

Error typePlain-language translationUseful check
NameError“This variable or function name does not exist here.”Is it misspelled, defined later, or defined in another scope?
TypeError“These kinds of values cannot be used together this way.”What are the actual types of the values?
KeyError“This dictionary does not contain that key.”Print or inspect the available dictionary keys.
IndexError“This list position does not exist.”Check list length and remember indexing starts at 0.
AttributeError“This object does not have that method or attribute.”Check the object’s type and the method name.
SyntaxError“Python cannot parse this code.”Inspect the marked line and nearby lines for missing punctuation, quotes, or indentation.

Example: TypeError can point to a data problem

Consider a reward bonus function:

def add_bonus(rewards):
    return [reward + 1 for reward in rewards]

rewards = [2, "3", 1]
print(add_bonus(rewards))

Python can add 1 to the first reward, 2. But when it reaches "3", it cannot add an integer to a string. A traceback will end with a message like:

TypeError: can only concatenate str (not "int") to str

The failing line is:

return [reward + 1 for reward in rewards]

However, the operation reward + 1 is reasonable if rewards are supposed to be numbers. The deeper issue is the mixed input list:

rewards = [2, "3", 1]

If the intended data is numeric, correct the input:

rewards = [2, 3, 1]
print(add_bonus(rewards))

Output:

[3, 4, 2]

For a larger dataset, you may not immediately see the bad value. Add a temporary diagnostic print:

print(rewards)
print([type(reward).__name__ for reward in rewards])

Output:

[2, '3', 1]
['int', 'str', 'int']

This reveals the mismatch directly.

A good debugging principle for research code is:

Fix the representation or assumption that is wrong; do not merely force the error message to disappear.

For example, converting every value with int() may be appropriate if a data source intentionally provides numeric text. It is not appropriate if "3" indicates an upstream data-quality problem that you should record and investigate.


Syntax errors versus runtime errors

Most tracebacks describe a runtime error: Python began running the program, then encountered a problem during execution.

A SyntaxError is different. Python cannot even understand the program structure, so it may not show the usual Traceback (most recent call last): heading.

For example:

def mean_reward(rewards)
    return sum(rewards) / len(rewards)

The function definition is missing a colon after ). Python will point to a line and often place a caret (^) near where it noticed the problem.

Two cautions help here:

  • The caret is a strong clue, but the true mistake can be slightly earlier on the same line or even on the line above, especially with quotes or brackets.
  • Fix syntax errors first. Until Python can parse the file, it cannot reveal later runtime problems.

This is also why it helps to run code in small increments. A short function with one error is much easier to debug than an entire notebook with ten unrelated changes.


Verification: “no traceback” is necessary, but not enough

After a correction, rerun the same input that originally failed. If the traceback disappears, that establishes that the immediate exception is gone.

Then check that the result is actually correct using a tiny example whose answer you know.

For mean_reward(), keep two lightweight checks directly below the function while you develop it:

def mean_reward(reward_list):
    return sum(reward_list) / len(reward_list)

assert mean_reward([2, 4, 6]) == 4.0
assert mean_reward([0.5, 1.0]) == 0.75

print("Checks passed")

If both assertions hold, the output is:

Checks passed

If an assertion fails, Python raises an AssertionError. That is useful: it means your code ran, but its result did not match the behavior you specified.

A practical verification sequence is:

CheckWhat it establishes
Rerun the original failing caseThe specific exception is resolved
Test a tiny hand-checkable caseThe core calculation produces the expected output
Inspect a representative outputThe type, length, labels, or values make sense
Keep the successful check near the codeA later change is less likely to silently break it

For ML experiments, this mindset prevents a common failure mode: a preprocessing script runs successfully but changes labels, scales rewards, or filters rows in a way you did not intend. Errors that stop the program are visible; incorrect outputs require deliberate checks.


A compact traceback protocol for your notes

Save this as a reusable debugging card in your vault:

Python Traceback Protocol

  1. Read the final line first: exception type plus message.
  2. Move upward to the closest File ..., line ..., in ... entry.
  3. Open that exact line and compare it to the surrounding definitions and input values.
  4. Explain the mismatch in one sentence before changing code.
  5. Apply the smallest intent-preserving fix.
  6. Rerun the original failing input.
  7. Verify one known expected output with assert or a direct comparison.

Common trap: removing an exception does not prove the program is correct.

If you use an editor such as VS Code, you can also add a breakpoint by clicking beside a line number, then inspect variable values while the program is paused. That is especially useful when a traceback identifies the failing line but the value that caused the problem was created much earlier. Start with tracebacks first; they are available in every normal Python run and often tell you enough.


Key takeaways

A traceback is Python’s structured report of an unhandled error. Read it from bottom to top:

  • The final line identifies the exception and gives the most immediate clue.
  • The nearest code frame above it identifies the file, line, function, and failing statement.
  • Earlier frames show how execution reached that line and may reveal where an unsuitable input originated.

Debug systematically: locate the failure, explain the mismatch, make a small correction that matches the program’s intent, then verify both that the traceback is gone and that a known input produces the expected output.

Next, you will bring together pseudocode, functions, collections, loops, and debugging by implementing and testing a short Python program—and explaining what each part does in your own words.

Can't find a good explanation? Sign up and we'll make it for you

Sign up