Skip to content
beginner

Debugging Python Code for Beginners

Every beginner hits this moment. The program doesn't crash — it just quietly does the wrong thing. You stare at the screen, re-read the code, and feel like…

Published 2026-09-05Updated 2026-09-1211 min read
Overhead view of a traditional leather tannery in Fes with various color dye pits.
Overhead view of a traditional leather tannery in Fes with various color dye pits. Photo by Ramon Karolan on Pexels.

Your code runs. No error message appears. And yet the answer is wrong.

Every beginner hits this moment. The program doesn't crash — it just quietly does the wrong thing. You stare at the screen, re-read the code, and feel like you're missing something obvious. You are. The question is how to find it.

Debugging is not a punishment for being a bad programmer. It's the skill of treating wrong output as evidence — a clue pointing to where your program's behavior and your expectations split apart. In this tutorial, you'll learn a repeatable workflow to debug Python code: read the error, trace the state, fix the assumption.

What Debugging Actually Means

Flowchart showing four debugging steps: observe the error or wrong output, inspect the traceback and variable values, find the assumption that diverged from reality, then fix the code and run it again.
Use this cycle to turn a Python failure or wrong result into evidence for your next debugging step.

Debugging is the process of identifying, analyzing, and resolving why code doesn't behave as expected. That sounds formal, but here's the practical version: something went wrong, and you need to find where.

Most beginner bugs aren't syntax errors. A syntax error stops the program immediately — Python tells you it can't understand your code. The trickier bugs are logic errors. The code runs fine, but the result is wrong. No error message appears because Python doesn't know your answer is incorrect. It just follows your instructions.

Think of it this way: a failure is evidence. It tells you where to look, not that you're failing. When a program returns the wrong value, somewhere between your intention and the output, an assumption broke down. Your job is to find that broken assumption.

Before we go further, a quick note: if you haven't yet learned how to read common Python errors like TypeError or NameError, that's a prerequisite worth reviewing. This tutorial builds on that foundation — turning an error message into a fix.

Here's the workflow we'll use throughout:

  1. Read the error. If there's a traceback, treat it as a map.
  2. Trace the state. Inspect what your variables actually contain at key points.
  3. Fix the assumption. Find where your mental model of the code diverged from reality.

Let's see it in action.

Knowledge check

Check your understanding

Answer this question before you continue.

A Python program runs successfully but produces the wrong result with no error message. What kind of bug does this most likely illustrate?
Single Choice

Focus: Distinguish a logic error from a syntax error based on how a program behaves.

Start With a Tiny Buggy Example

Here's a short function that calculates the average of three test scores. It runs without errors. It just returns the wrong answer.

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score
    average = total / len(scores) - 1
    return average

scores = [85, 90, 78]
result = calculate_average(scores)
print(f"The average score is: {result}")

Save this as average.py and run it:

python average.py
The average score is: 83.33333333333333

Before reading further, predict what you expect the output to be. If you add 85, 90, and 78, you get 253. Divide by 3, and you get 84.33. The program printed 83.33 instead. No error. No warning. Just a wrong number.

That gap between your prediction and the actual output is where the bug lives. Making a prediction matters — it forces you to notice when reality doesn't match your mental model.

Knowledge check

Check your understanding

Answer this question before you continue.

What does this function return for the given input?
Output Prediction

Focus: Predict the result of a buggy average calculation by tracing its arithmetic.

```python
def calculate_average(scores):
    total = 0
    for score in scores:
        total += score
    return total / len(scores) - 1

print(calculate_average([85, 90, 78]))
```

Use print() to See What Your Code Is Doing

When no error message appears, you need to inspect values. The simplest tool for this is print().

A print() call is a window into your program's state. It shows you what a variable actually contains at the moment the line runs — not what you assume it contains.

Let's add some print statements to our buggy function:

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score
        print(f"After adding {score}, total is: {total}")

    average = total / len(scores) - 1
    print(f"Total: {total}, Count: {len(scores)}")
    print(f"Calculated average: {average}")
    return average

scores = [85, 90, 78]
result = calculate_average(scores)
print(f"The average score is: {result}")
After adding 85, total is: 85
After adding 90, total is: 175
After adding 78, total is: 253
Total: 253, Count: 3
Calculated average: 83.33333333333333
The average score is: 83.33333333333333

Now the bug is visible. The total is 253 and the count is 3. If you divide 253 by 3, you get 84.33. But the function subtracted 1 from the division. The - 1 on the average line is the broken assumption.

Here's the decision rule I use when debugging Python code: print the inputs, print the intermediate results, print the output. One of them will betray the wrong assumption.

In this case, printing the total and count made the problem obvious. The fix is simple:

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score

    average = total / len(scores)
    return average

One warning before we move on: remove your debug prints before you call the code finished. They clutter the output and can make a real program harder to read. Comment them out or delete them once the bug is fixed.

Knowledge check

Check your understanding

Answer this question before you continue.

For the buggy average function in the article, which debug output most directly helps reveal the wrong assumption?
Single Choice

Focus: Choose useful state values to inspect when diagnosing a silent calculation bug.

Read the Error Message Like a Map

Print statements handle the silent bugs. But what about the crashes — the moments when Python throws a traceback at you?

A traceback looks like a wall of text, but it's really a map. It shows the path your code took before it failed.

Traceback (most recent call last):
  File "scores.py", line 8, in <module>
    result = calculate_average(scores)
  File "scores.py", line 4, in calculate_average
    total += score
TypeError: unsupported operand type(s) for +=: 'int' and 'str'

Here's how to read it:

  • The final line names the error type and message. In this case, Python tried to add a string to an integer with +=.
  • The lines above show the call path — the sequence of function calls that led to the failure. The first frame listed is where the error happened. The frames after it show how execution reached that point.

My rule for beginners: read the final line first to learn what failed. Then scan upward to find the first line that belongs to code you wrote. That's where you start investigating.

In this example, the error happened inside calculate_average on line 4. But the fix isn't necessarily on that line. The traceback points to where Python noticed the problem. Your job is to trace backward to where the wrong value entered the program.

Knowledge check

Check your understanding

Answer this question before you continue.

According to the article's beginner rule, what should you do first when reading a traceback?
Misconception Check

Focus: Use a traceback to identify where to begin investigating a runtime failure.

A Common Beginner Mistake: Debugging the Wrong Line

Here's the trap I see beginners fall into most often: the traceback points to a line, so they assume the mistake is on that line.

Often, it isn't.

The traceback points to where Python noticed the problem. The actual mistake might be several lines earlier — or in a completely different function.

Consider this example:

def get_scores():
    return ["85", "90", "78"]

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score
    return total / len(scores)

scores = get_scores()
result = calculate_average(scores)
print(result)
Traceback (most recent call last):
  File "scores.py", line 12, in <module>
    result = calculate_average(scores)
  File "scores.py", line 7, in calculate_average
    total += score
TypeError: unsupported operand type(s) for +=: 'int' and 'str'

The error appears on line 7, inside calculate_average. But the real problem is on line 2: get_scores() returns strings, not integers. The function calculate_average is doing exactly what it was told. The wrong data entered the program earlier.

The fix is to trace backward. Ask: where did this value come from? What was it before it reached this line?

This backward tracing is a skill, and it takes practice. When you catch yourself staring at the failing line, stop. Look up the call path. Find where the value first went wrong. That's where the fix lives.

Try IDE Debugger Tools: Breakpoints

Print statements work, but they have a cost. You add code, run the program, read the output, then remove the code. For a small script, that's fine. For a bigger program, it gets tedious.

That's where breakpoints come in.

A breakpoint is a pause button. You tell your editor to stop the program at a specific line. When execution reaches that line, the program freezes, and you can inspect the current values of your variables — without adding a single print() call.

Let's walk through one concrete session using VS Code, a free editor that works well for Python. If you use a different editor like PyCharm, the same ideas apply — the buttons just live in different places.

Start with our buggy average script:

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score
    average = total / len(scores) - 1
    return average

scores = [85, 90, 78]
result = calculate_average(scores)
print(f"The average score is: {result}")

Here's your first debugger session:

  1. Click in the gutter — the narrow area just left of the line numbers — next to the line average = total / len(scores) - 1. A red dot appears. That's your breakpoint.
  2. Open the Run and Debug panel in the sidebar, then click Run and Debug. Choose Python File if prompted.
  3. The program starts and stops at your breakpoint. The line turns yellow, meaning it hasn't executed yet.
  4. Look at the Variables panel on the left. You'll see total is 253 and scores is [85, 90, 78]. Those values are correct.
  5. Click Step Over once. The yellow highlight moves to the return average line, and the Variables panel now shows average is 83.33333333333333.

There's your bug, caught in the act. The inputs were right, but the calculation produced the wrong result. You can now see exactly which line created the bad value — without adding a single print statement.

The contrast with print() is worth noting:

ApproachWhat it doesBest for
print()Shows values at specific pointsQuick checks in small scripts
BreakpointsPauses execution so you can inspect anythingLarger programs where you don't want to edit code repeatedly

If you don't have an IDE handy, Python ships with a built-in debugger called pdb. You can insert a breakpoint directly into your code:

def calculate_average(scores):
    total = 0
    for score in scores:
        total += score

    import pdb
    pdb.set_trace()

    average = total / len(scores)
    return average

When the program reaches pdb.set_trace(), it stops and gives you an interactive prompt where you can inspect variables and step through the code.

For this tutorial, the goal is a first successful action, not mastery. Set one breakpoint. Inspect one variable. Step over one line. Once you've done that, you know enough to explore further on your own — the skill transfers across tools.

Practice: Debug a Broken Script

Time to apply the workflow yourself. Here's a script with a logic bug. It's supposed to count how many even numbers are in a list.

numbers = [2, 7, 10, 15, 22, 31, 40]
even_count = 0

for number in numbers:
    if number % 2 == 1:
        even_count += 1

print(f"Number of even values: {even_count}")

The expected output is 4 — the even numbers are 2, 10, 22, and 40. Run the script and see what you actually get.

Here's the workflow to follow:

  1. Run it. Observe the actual output.
  2. Predict. Before you change anything, write down what you expect each variable to contain.
  3. Trace. Add print() statements to inspect number and even_count inside the loop.
  4. Find the wrong assumption. Look at the condition. What does number % 2 == 1 actually check?
  5. Fix it. Make the smallest change that produces the correct result.

There's no single right fix. Any correct, readable solution counts. The point is to practice the loop: read the error, trace the state, fix the assumption.

Your Next Step

You now have a repeatable workflow for debugging Python code. When a script misbehaves, don't stare at it. Run it. Read the error. Print the values. Find the broken assumption.

Then try this workflow on your own scripts. The more you practice, the faster the pattern becomes — and the less intimidating bugs feel.

The natural next skill is catching errors before they surprise you. That means writing simple tests: small checks that confirm your functions return the right answers. When you're ready, that's where to go next.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

In the article's `get_scores()` example, where is the underlying problem that causes `total += score` to fail?
Question 1 of 2Debugging

Focus: Trace a bad value backward to where it entered a program instead of assuming the failing line contains the fix.

Which tool does the article describe as pausing execution at a chosen line so you can inspect variables without adding a `print()` call?
Question 2 of 2Single Choice

Focus: Select the debugging tool that pauses execution and allows variable inspection without adding print statements.

References

  1. pdb — The Python Debugger — Python 3.14.7 documentationdocs.python.org
  2. Python debugging in VS Codecode.visualstudio.com
8sources checked
8source domains
5searches run

Research updated Sep 5, 2026

Practical resource

Want a more structured Python path?

Use the Python Starter Pack to turn scattered tutorials into a focused practice path.

View the bundle
Coming soon

Python Starter Pack

A compact LearnPyFast PDF pack covering what Python is, installation, your first program, running Python code, and Python versions.

$9
PDF BundleTopic PackPythonBeginner
  • 5 curated chapters
  • Enhanced PDF edition with bundle-only learning guidance
  • Offline-friendly format for focused review
  • Source article links for future online updates

Coming soon

Free Python bundle

Get the LearnPyFast Python Starter Bundle

A focused collection of beginner-friendly Python resources to help you move from setup to building practical projects.

You’ll receive the bundle by email. You can unsubscribe anytime.

No spam. You can unsubscribe anytime. See our Privacy policy.

Related sites

Continue beyond Python

Explore related Worldmonger sites when you want to move from Python basics into JavaScript or LLM application building.

JavaScript tutorialstutorial

LearnJSFast

Beginner-friendly JavaScript tutorials for practical web development and self-taught developers.

JavaScriptFrontendWeb development
Visit LearnJSFast
LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast

Keep learning

Related tutorials

Continue with nearby Python topics and beginner-friendly explanations.