Python Debugging
Learn how to systematically find, understand and fix bugs in Python programs using tracebacks, print debugging, assertions, breakpoints and professional debugging techniques.
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.
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.
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?"
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.
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
- What exception occurred?
- Which line caused it?
- What values were involved?
- Why did those values produce the problem?
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.
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.
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.
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.
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?
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
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.
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.
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
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:
- Why is
countzero? - Should zero be allowed?
- Where did
countcome from? - 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.
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.
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
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.
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.
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
- Run the program.
- Read the error message.
- Identify the incorrect variable.
- Fix the calculation.
- 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.
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?
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. -
assertcan 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.