Debugging B: The Debugging Process and the VS Code Debugger
- Apply the five steps of debugging — Reproduce, Isolate, Root Cause, Fix, Verify
- Set a Breakpoint and run code in Debug mode in VS Code
- Use Step Over, Step Into and Continue, and follow variables in the Variables window
- Find and fix a Logical Error in real code with the help of the Debugger
What will we learn?
In the previous lesson we learned to read errors. Now we will learn to hunt bugs, including ones that have no error message:
- A process — five fixed steps for every bug.
- A tool — the VS Code Debugger: stopping the code in the middle and checking what happens inside it.
- Real practice — together we will solve a bug step by step.
Why do we need this?
The beginners’ way is “guess and change”: change something, run, and if it did not work, change something else. It is slow, frustrating, and often creates a new bug. Professionals work with a method, and the Debugger lets you see the code “in slow motion”, instead of guessing what happens in it.
Explanation from the basics
The five steps of debugging
| Step | Name | The question it answers |
|---|---|---|
| 1 | Reproduce | How do you make the bug happen every time? With which input? |
| 2 | Isolate | Where in the code is the problem? Narrow it down to a line or a function. |
| 3 | Root Cause | Why does it happen? Not the symptom, but the cause. |
| 4 | Fix | Fix the cause, and do not hide the symptom. |
| 5 | Verify | Is the bug gone? And did nothing else break? |
A good doctor does not give every patient a painkiller. They reproduce (“when does it hurt?”), isolate (“where exactly?”), find the cause (tests), treat the cause, and check at a follow-up visit that the patient recovered. A painkiller alone is “fixing the symptom”. The pain comes back, and the problem continues.
What is a Debugger?
A Debugger is a tool that runs the program under your control: it stops it at a line you chose, and lets you advance line by line and see the value of every variable at every moment.
The terms:
| Term | What it is |
|---|---|
| Breakpoint | A “stopping point”: a mark on a line. When the run reaches it, the program stops before executing the line. |
Continue (F5) | Continue running normally, until the next Breakpoint (or until the end). |
Step Over (F10) | Execute the current line in full and stop at the next line. If it contains a function call, the function runs “all at once”. |
Step Into (F11) | If the line contains a function call, go inside it and stop at its first line. |
Step Out (Shift+F11) | Finish the current function and return to whoever called it. |
| Variables | A window that shows all the variables and their values at this moment. |
| Watch | Expressions you chose to follow, for example total / count. |
| Call Stack | The chain of calls: which function called which. It is the Traceback, only “live”. |
Stop (Shift+F5) | Stop debugging. |
You are reading a recipe, and it has a line “prepare the dough (see page 40)”. Step Over = you prepare the dough without opening page 40, and move on to the next line of the recipe. Step Into = you open page 40 and read the dough recipe line by line.
Syntax
How to start the Debugger in VS Code:
- Make sure Microsoft’s Python extension is installed. It includes the Debugger.
- Open a
.pyfile. - Setting a Breakpoint: click in the left margin, to the left of the line number. A red dot 🔴 appears (or
F9on the line). - Starting:
F5, or the small arrow next to the ▷ button → Python Debugger: Debug Python File. If asked which configuration, choose Python File. - The program stops at the Breakpoint. The yellow line is the next line to be executed.
- On the left side appear Variables, Watch and Call Stack. At the top appears the button bar: Continue, Step Over, Step Into, Step Out, Restart, Stop.
Trick: you can also hover the mouse over a variable in the code while stopped, and see its value.
First code example
We will start with a correct program, just to get to know the tool. week-2/debug_intro.py:
def apply_tax(amount):
tax = amount * 0.17
return amount + tax
prices = [100, 250, 40]
total = 0
for price in prices:
total = total + apply_tax(price)
print("Total with tax:", total)The exercise:
- Put a Breakpoint on the line
total = total + apply_tax(price). F5. The program stops. In the Variables window:price = 100,total = 0.F10(Step Over). Nowtotal = 117.0, and the yellow line went back tofor.F5(Continue). We stop again on the same line:price = 250.- This time
F11(Step Into). We are insideapply_tax, andamount = 250. In the Call Stack we see:apply_tax→<module>. F10twice: we seetax = 42.5, and thenreturn.F5until the end.
Output:
Total with tax: 456.3Breaking down the code line by line
What we see at each stop, and that is how we understand the flow:
| Stop | Yellow line | Variables | Explanation |
|---|---|---|---|
| 1 | total = total + apply_tax(price) | price=100, total=0 | The stop is before the line is executed, so total is still 0. |
After F10 | for price in prices: | total=117.0 | The line was executed in full, including the function. |
| 2 | total = total + apply_tax(price) | price=250, total=117.0 | Second round. |
After F11 | tax = amount * 0.17 | amount=250 | We went into the function. We see only its variables (Local). |
After F10 | return amount + tax | amount=250, tax=42.5 |
Another example
A real bug — from start to finish. A program that calculates a grade average without the lowest grade (as is common in courses: “the lowest grade is not counted”). week-2/drop_lowest.py:
def find_lowest(grades):
lowest = 0
for grade in grades:
if grade < lowest:
lowest = grade
return lowest
def average_without_lowest(grades):
lowest = find_lowest(grades)
total = sum(grades) - lowest
return total / (len(grades) - 1)
my_grades = [80, 90, 70, 100]
print("Average without lowest:", average_without_lowest(my_grades))Expectation: the lowest is 70. That leaves 80, 90, 100, and the average is 90.0.
Actual output:
Average without lowest: 113.33333333333333An average above 100! This is a Logical Error: no error message, just an impossible result.
Step 1 — Reproduce: the input [80, 90, 70, 100] produces the bug on every run. Excellent, we have a stable case.
Step 2 — Isolate: there are two functions. Which one is guilty? Put a Breakpoint on the line total = sum(grades) - lowest and run with F5. In the Variables window: lowest = 0. But the lowest is 70! The problem is already before this line, inside find_lowest. We isolated it.
Step 3 — Root Cause: stop, put a Breakpoint on lowest = find_lowest(grades), F5, then F11 (Step Into) into find_lowest, and F10 line by line:
| Round | grade | grade < lowest? | lowest |
|---|---|---|---|
| Start | — | — | 0 |
| 1 | 80 | 80 < 0 → False | 0 |
| 2 | 90 | 90 < 0 → False | 0 |
| 3 | 70 | 70 < 0 → False | 0 |
| 4 | 100 | 100 < 0 → False | 0 |
The root cause: lowest starts at 0. No positive grade is smaller than 0, so it is never updated. The assumption “0 is a good starting value” is wrong.
Step 4 — Fix: start from the first grade in the list. It is always a real candidate:
def find_lowest(grades):
lowest = grades[0]
for grade in grades:
if grade < lowest:
lowest = grade
return lowest(Python also has a built-in min(grades), and in real code it is better to use it. Here we wrote it by hand for practice.)
Step 5 — Verify:
Average without lowest: 90.0And check more cases, not only the original one:
[50, 50, 50]→50.0✓[100, 0]→100.0✓ (the lowest, 0, is removed)[70]→ crashes!ZeroDivisionError(dividing bylen - 1 = 0). We found another bug thanks to the verify step. What to do with a list of one grade? That is a business decision. For example: return the grade itself.
Common mistake
Mistake 1: skipping Reproduce. “The user said it sometimes does not work”, so you change code without a case that reproduces the bug. Without reproducing it, there is no way to know you fixed it.
Mistake 2: debugging with print only, without cleaning up. Temporary prints (print("HERE", x)) are a legitimate, fast tool, but they get forgotten in the code and clutter the output. The Debugger shows all the variables without changing the code. (And in the exceptions and logging topic we will learn Logging, the professional way to leave information in the code.)
Mistake 3: Step Into everything. F11 on print(...) or sum(...) takes you into Python’s internal code, and it is easy to get lost there. Use Step Into on your own functions, and Step Over on built-in functions. (If you went in by mistake, Shift + F11 takes you out.)
Mistake 4: fixing and not checking more cases. As we saw, the Verify step revealed a second bug. Always check edge cases too: an empty list, one item, negative values, zero.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: The five steps
Put the debugging steps in order, and explain in one sentence what you do in each step:
Fix · Reproduce · Verify · Understand · Isolate
Show solution
- Reproduce — make the bug happen again, on purpose, with a known input.
- Isolate — narrow it down: in which function, on which line, with which value the problem starts.
- Understand — understand why it happens, before changing any code.
- Fix — fix the root cause, not the symptom.
- Verify — run the case that failed again, and also cases that worked before.
Exercise 2: The maximum that came out 0
def find_max(numbers):
largest = 0
for number in numbers:
if number > largest:
largest = number
return largest
print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))The second call returns 0, even though 0 is not in the list at all.
- Where would you put a Breakpoint, and what would you see in the Variables window?
- Fix the bug.
Expected output:
9
-3Show solution
- A Breakpoint on the line
if number > largest:. In the Variables window you see thatlargeststays 0 throughout the loop — no negative number is greater than 0. - Start from the first item of the list, not from 0:
def find_max(numbers):
largest = numbers[0]
for number in numbers:
if number > largest:
largest = number
return largest
print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))9
-3Exercise 3: return inside the loop
def count_vowels(word):
count = 0
for letter in word:
if letter in "aeiou":
count = count + 1
return count
print(count_vowels("banana"))The function returns 0 instead of 3. Use Step Over to follow the loop — what happens after the first letter? Fix it.
Expected output:
3Show solution
With Step Over you see that after the first letter (b, not a vowel) the run goes straight to return count — inside the loop — and the function ends with 0. The return is indented one level too deep.
def count_vowels(word):
count = 0
for letter in word:
if letter in "aeiou":
count = count + 1
return count
print(count_vowels("banana"))3Exercise 4: The Debugger’s tools
Explain each term in one sentence, and when you use it:
- Breakpoint
- Step Over
- Step Into
- The Variables window
Show solution
- Breakpoint — a point where the run stops, so you can check the state at that moment. Put it on the line before the suspicious place.
- Step Over — runs the current line and moves to the next one, without going into functions called on it.
- Step Into — goes into the function called on the current line, to check what happens inside.
- The Variables window — shows the values of all the variables at the current moment. This is where you see where a value differs from what you expected.
Exercise 5: Off-by-one
def sum_to(n):
total = 0
for i in range(1, n):
total = total + i
return total
print(sum_to(5))The sum 1+2+3+4+5 should be 15, but 10 comes out. Find the bug and fix it.
Expected output:
15Show solution
range(1, n) does not include n — the loop runs on 1, 2, 3, 4 only. This is a classic “Off-by-one” bug.
def sum_to(n):
total = 0
for i in range(1, n + 1):
total = total + i
return total
print(sum_to(5))15Summary
- The five steps: Reproduce → Isolate → Root Cause → Fix → Verify. No guessing.
- A Breakpoint stops before the line is executed. F5 = Continue, F10 = Step Over, F11 = Step Into, Shift+F11 = Step Out.
- The Variables window shows the state of the program at the moment of the stop. The Call Stack shows who called whom.
- You find Logical Errors by comparing the actual values of the variables with the expected values, line by line.
- After a fix, also check edge cases: empty, one item, zero, negative.