Lesson 58 · Python Development Tools 45–55 minutes Intermediate

Python Debugging

Learn how to systematically find, understand and fix bugs in Python programs using tracebacks, print debugging, assertions, breakpoints and professional debugging techniques.

0%
01

Learning Objectives

Debugging is a core programming skill. Professional developers spend significant time investigating and fixing unexpected behavior.

You will learn how to:

  • Understand what debugging means.
  • Identify different types of bugs.
  • Read Python error messages.
  • Understand traceback information.
  • Use print() for debugging.
  • Use assertions to detect incorrect assumptions.
  • Use breakpoints in VS Code.
  • Step through a program line by line.
  • Inspect variables and the call stack.
  • Debug logical errors systematically.
  • Develop professional debugging habits.
02

What Is Debugging?

Debugging is the process of finding, understanding and fixing problems in a program.

A problem in a program is commonly called a bug.

Debugging is not simply changing random lines until the program works. Good debugging follows a systematic process.

Observe the problem
        ↓
Reproduce the problem
        ↓
Find the cause
        ↓
Understand the cause
        ↓
Fix the cause
        ↓
Test the solution

Key Idea

The goal of debugging is not merely to remove the error message. The goal is to understand why the program behaved incorrectly.

03

Why Debugging Matters

Even experienced programmers write code containing mistakes.

Large applications can contain thousands or millions of lines of code, making systematic debugging essential.

Strong debugging skills help developers:

  • Find problems faster.
  • Understand unfamiliar code.
  • Reduce repeated mistakes.
  • Improve software reliability.
  • Diagnose production issues.
  • Understand how programs execute.

Professional Mindset

Instead of asking, "What line should I change?", ask: "What evidence tells me where the problem actually is?"

04

Types of Bugs

Python problems can appear in several forms.

Syntax Errors

These happen when Python cannot understand the structure of your code.

if age >= 18
    print("Adult")

The missing colon causes a syntax error.

Runtime Errors

These occur while the program is executing.

number = 10 / 0

This produces a ZeroDivisionError.

Logical Errors

Logical errors are particularly interesting because the program runs but produces the wrong result.

price = 100
discount = 20

final_price = price + discount

print(final_price)

The code runs, but the intended calculation was probably subtraction.

Logical Errors Can Be Harder

A program that crashes immediately tells you something is wrong. A program that silently produces incorrect results can be much harder to detect.

05

Reading Error Messages

When Python raises an exception, do not immediately ignore the error message.

Read it carefully.

Traceback (most recent call last):
  File "app.py", line 5, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero

The final line tells you the exception type and a useful description.

First Questions to Ask

  1. What exception occurred?
  2. Which line caused it?
  3. What values were involved?
  4. Why did those values produce the problem?
06

Understanding Tracebacks

A traceback shows the path Python followed before the exception occurred.

def calculate():
    return divide(10, 0)

def divide(a, b):
    return a / b

calculate()

Python reports the chain of function calls leading to the error.

Traceback
    calculate()
        ↓
    divide(10, 0)
        ↓
    a / b
        ↓
ZeroDivisionError

Important

The last line of the traceback is often the most important starting point, but earlier lines show how execution reached the problem.

07

Debugging with print()

One of the simplest debugging techniques is displaying important values during program execution.

price = 100
quantity = 3

print("price:", price)
print("quantity:", quantity)

total = price * quantity

print("total:", total)

This helps you verify whether variables contain the values you expect.

Debugging Conditions

age = 17

print("age:", age)
print("adult:", age >= 18)

if age >= 18:
    print("Access granted")
else:
    print("Access denied")

Do Not Leave Debug Prints Everywhere

Temporary print statements are useful while investigating a problem, but unnecessary debugging output should normally be removed or replaced with proper logging.

08

Using assert

The assert statement checks whether an assumption is true.

age = 25

assert age >= 0

print("Valid age")

If the condition is false, Python raises AssertionError.

age = -5

assert age >= 0

You can provide a message:

age = -5

assert age >= 0, "Age cannot be negative"

Important Distinction

Assertions are useful for checking programmer assumptions and internal conditions. They should not be treated as a replacement for normal validation of untrusted user input.

09

Debugging in an IDE

Modern development environments provide powerful debugging tools.

VS Code, for example, can allow you to pause execution, inspect variables and execute your program one line at a time.

Why Use a Debugger?

Instead of adding many temporary print statements, you can pause the program exactly where you want and inspect its state.

10

Breakpoints

A breakpoint tells the debugger to pause program execution at a specific line.

price = 100
quantity = 5

total = price * quantity

print(total)

You could place a breakpoint on the total calculation.

When execution reaches that line, the debugger pauses and allows you to inspect the current state.

Useful Questions at a Breakpoint

  • What is the current value of each variable?
  • Which function am I inside?
  • What line will execute next?
  • Did the program reach this point as expected?
11

Stepping Through Code

A debugger normally provides controls for moving through program execution.

Step Over

Executes the current line without entering the internal implementation of a called function.

Step Into

Enters a function so you can inspect its execution.

Step Out

Finishes the current function and returns to its caller.

Conceptual Flow

Breakpoint
    ↓
Step Over
    ↓
Step Into function
    ↓
Inspect function
    ↓
Step Out
    ↓
Continue execution
12

Inspecting Variables

When debugging, variable values are often the most important evidence.

items = 4
price = 25

total = items * price

print(total)

At the breakpoint, inspect:

items = 4
price = 25
total = 100

If you expected items = 5, you have learned something important about the source of the problem.

Debugging Principle

Do not guess variable values. Inspect them.

13

Understanding the Call Stack

The call stack represents the chain of function calls currently active in the program.

main()
  ↓
process_order()
  ↓
calculate_total()
  ↓
apply_discount()

If an error occurs inside apply_discount(), the call stack helps you understand how execution arrived there.

Why It Matters

A bug may appear deep inside a function even though the incorrect value was originally created much earlier in the call chain.

14

Conditional Breakpoints

Sometimes a loop executes hundreds or thousands of times, but you only care about one particular case.

for order in orders:
    process_order(order)

Instead of stopping every time, you can use a condition such as:

order["id"] == 5001

The debugger pauses only when the condition becomes true.

When Are They Useful?

  • Large loops
  • Specific customer records
  • Specific transaction IDs
  • Rare edge cases
15

Debugging Exceptions

Consider:

def calculate_average(total, count):
    return total / count

print(calculate_average(500, 0))

Python raises:

ZeroDivisionError: division by zero

A good debugging process asks:

  1. Why is count zero?
  2. Should zero be allowed?
  3. Where did count come from?
  4. Should the function validate it?

A possible fix:

def calculate_average(total, count):

    if count == 0:
        return 0

    return total / count

The correct solution depends on the application's requirements.

16

Debugging Logic Errors

Logic errors require a different approach because the program may run successfully.

marks = [80, 90, 70]

total = sum(marks)

average = total / len(marks)

if average > 90:
    grade = "A"
else:
    grade = "B"

print(average)
print(grade)

The code executes, but you must determine whether the grading rules match the intended requirements.

Useful Technique: Work Backwards

Start with the incorrect result and trace backward: which calculation produced it, which values created that calculation, and where those values originated.

17

Professional Debugging Strategies

1. Reproduce the Bug

Make the problem happen consistently whenever possible.

2. Reduce the Problem

Remove unrelated code and isolate the smallest example that still produces the problem.

3. Check Assumptions

Ask yourself what you believe should happen and verify those assumptions against actual values.

4. Change One Thing at a Time

If you change ten things simultaneously, you may not know which change solved or introduced the problem.

5. Test the Fix

A fix should be tested against both the original problem and related scenarios.

6. Look for the Root Cause

Fixing a symptom without understanding the root cause can cause the same problem to return elsewhere.

Golden Rule

Observe → Hypothesize → Test → Fix → Verify

18

Common Debugging Mistakes

1. Guessing Instead of Investigating

Randomly changing code can hide the real cause.

2. Ignoring the Traceback

Python often gives you valuable information about what went wrong.

3. Fixing Only the Symptom

The same underlying problem may appear again.

4. Changing Too Many Things

Make controlled changes so you can identify cause and effect.

5. Not Testing the Fix

A change that fixes one input might break another.

Remember

Debugging is a reasoning process, not a guessing game.

19

Practice Exercises

Exercise 1 — Find the Runtime Error

numbers = [10, 20, 30]

print(numbers[5])

Identify the exception and explain why it occurs.

Exercise 2 — Find the Logic Error

price = 500
discount = 10

final_price = price + (price * discount / 100)

print(final_price)

The intended behavior is to calculate the price after a discount. Find and fix the logic error.

Exercise 3 — Debug with print()

quantity = 4
price = "25"

total = quantity * price

print(total)

Add debugging output and determine what type of value price contains.

Exercise 4 — Use assert

Write a function that accepts an age and uses assert to verify that the age is not negative.

20

Mini Challenge — Debug the Sales Analyzer

The following program is supposed to calculate the total sales and average sale amount.

sales = [1200, 800, 500, 300]

total = 0

for sale in sales:
    total += sales

average = total / len(sales)

print("Total:", total)
print("Average:", average)

Your Mission

  1. Run the program.
  2. Read the error message.
  3. Identify the incorrect variable.
  4. Fix the calculation.
  5. Verify the result.

Expected Result

Total: 2800
Average: 700.0

Challenge Extension

Add validation so that an empty sales list does not cause a division-by-zero error.

Then use a debugger breakpoint inside the loop and inspect sale and total as the program executes.

21

Knowledge Check

1. What is debugging?

2. Which error occurs while code is executing?

3. What does a traceback provide?

4. What does a breakpoint do?

5. What is a logical error?

6. What is assert useful for?

7. What is a good debugging practice?

8. Why inspect variables during debugging?

22

Lesson Summary

Debugging is one of the most valuable practical skills a Python developer can develop. Instead of guessing, professional debugging relies on evidence and systematic investigation.

What You Learned

  • Debugging means finding and fixing program problems.
  • Bugs can be syntax, runtime or logical errors.
  • Python tracebacks contain valuable diagnostic information.
  • print() can help inspect program state.
  • assert can check programmer assumptions.
  • Breakpoints pause program execution.
  • Stepping allows you to execute code gradually.
  • Variable inspection helps reveal unexpected values.
  • The call stack shows active function calls.
  • Conditional breakpoints are useful for large loops and specific cases.
  • Good debugging follows a systematic process.

The next lesson builds on this development workflow by introducing Python Testing — how to automatically verify that your code behaves correctly.

Ready to Continue?

You have completed the Python Debugging lesson.