Unit 07 · lesson
Debugging With VS Code
Core path: 30 minutes
What if you could pause Python in the middle of execution and inspect the program before the wrong result appears?
That is what a debugger gives you: controlled visibility into a running program.
It does not solve the bug for you. It lets you stop guessing about state.
A breakpoint is a planned observation point
Suppose:
score = int(input("Score: "))
rank = determine_rank(score)
print(rank)
Put a breakpoint on:
rank = determine_rank(score)
The breakpoint tells the debugger:
Pause when execution reaches this line so I can inspect the current state before continuing.
That is different from adding a permanent print() statement to the application.
A breakpoint is an investigation tool.
Stop with a question
Before clicking the gutter, finish this sentence:
I am pausing here because I need to know whether ________.
Examples:
score is actually 95
score is an int instead of a string
the program reaches this branch
total changed after the previous line
A breakpoint without a question can turn debugging into sightseeing.
What happens when the debugger pauses?
At a breakpoint, the program has executed some statements and has not yet executed later statements.
That timing matters.
Suppose:
x = 10
y = x + 5 # breakpoint here
z = y * 2
Depending on exactly where the debugger reports the pause, the current line is usually the next statement to execute.
So you may see:
x = 10
y not updated by this line yet
z not created yet
After stepping over y = x + 5, inspect again:
y = 15
The debugger makes state transitions visible one line at a time.
Learn the core controls by behavior
Continue
Resume normal execution until the next breakpoint, exception, or program end.
Use this when you do not need to inspect every intermediate line.
Step Over
Execute the current line and pause at the next line in the current frame.
If the line calls a function, Step Over lets that function run without walking through its internals line by line.
Use it when the function is not the thing you are investigating.
Step Into
If the current line calls a function:
result = calculate_total(12.50, 3)
Step Into moves the investigation inside:
def calculate_total(price, quantity):
...
Now you can inspect parameter binding and local state inside the function.
Step Out
When you have learned what you need inside the function, Step Out can continue until that function returns to its caller.
That keeps you from manually stepping through thirty lines you no longer need to inspect.
The Variables view is a snapshot of state
When paused, inspect names such as:
score
battery
status
total
player
Ask:
- Is the value what I predicted?
- Is the type what I predicted?
- Has the variable been created yet?
- Did a previous statement update it?
- Is this variable local to the current function?
The debugger is useful because those answers come from the running program, not your memory of the code.
Follow a function call through the boundary
Use:
def calculate_total(price, quantity):
subtotal = price * quantity
return subtotal
result = calculate_total(12.50, 3)
print(result)
Set a breakpoint on the call.
Then Step Into.
Watch:
price = 12.50
quantity = 3
Those parameter values did not exist inside the function before the call. The call created the function frame and bound the arguments to local parameter names.
Step until subtotal becomes 37.5.
Then step over return subtotal and observe control return to the caller where result receives the returned value.
This is Week 6's function-flow model, now visible in an actual runtime.
Use the call stack when execution crosses functions
When paused inside nested calls, the debugger can show the call stack: the chain of active function calls that led to the current point.
Conceptually:
main program
↓ called
build_report()
↓ called
calculate_average()
↓ paused here
That is the interactive version of the traceback path you read in Lesson 1.
Traceback: path after a failure.
Debugger call stack: path while the program is still paused and alive.
Watch one branch decision
Use:
def determine_rank(score):
if score >= 50:
return "ADVANCED"
elif score >= 90:
return "ELITE"
return "ROOKIE"
Call with 95.
Set a breakpoint at the first condition and step.
Observe:
score = 95
score >= 50 → True
The debugger will show execution returning from that first branch before the later elif gets a chance.
The debugger did not explain the policy error. It exposed the execution evidence that supports the explanation.
Tools provide visibility. You still do the reasoning.
Debug a running total
total = 0
for score in [10, 20, 30]:
total += score
print(total)
Set a breakpoint on:
total += score
For each pause, record:
| iteration | score | total before step | total after step |
|---|---|---|---|
| 1 | |||
| 2 | |||
| 3 |
Use Step Over to execute the update and watch the state change.
Now intentionally move total = 0 inside the loop and repeat the investigation.
The debugger will make the reset visible on every iteration.
Debugger versus debug prints
Use debug prints when:
- the program is tiny;
- you need quick evidence in a simple path;
- the environment does not provide a debugger;
- you want persistent output from many iterations.
Use a debugger when:
- state changes across several lines;
- function boundaries matter;
- you need to inspect multiple variables at once;
- adding temporary prints would distort the program or create noise.
Both are inspection techniques.
Do not turn this into a tool religion.
Real interface recognition
The actual location of VS Code controls can change across versions and layouts. Your class may use a screenshot or local demonstration to identify:
- breakpoint gutter;
- Run and Debug panel;
- Variables pane;
- Call Stack pane;
- Continue / Step Over / Step Into / Step Out controls.
The labels may move. The debugging jobs remain the same.
Before Lesson 4
You should be able to set a breakpoint with a specific question, inspect state, step through one change, follow a function call, and explain what the call stack represents.
Then the broken-system investigation uses those tools on code you did not author from scratch.
Vocabulary lab
Flip the idea, not just the card
Explain the term before you reveal the back. Then compare your explanation with the definition, example, and warning.
Read all terms without animation
- Breakpoint
- A debugger marker that pauses execution at a selected source location so runtime state can be inspected. Example: Pausing before determine_rank(score) executes. Do not confuse it with: A print statement that permanently adds output to the running program.
- Step Over
- Execute the current statement and pause at the next statement in the current frame without stepping through called functions line by line. Example: Run calculate_total(...) as one step when its internals are not under investigation. Do not confuse it with: Step Into, which enters the called function.
- Step Into
- Move debugging execution into a function called by the current statement. Example: Enter calculate_total() to inspect price and quantity parameters. Do not confuse it with: Continue, which resumes until another breakpoint or stop event.
- Call Stack
- The active chain of function calls that led to the current paused execution point. Example: main → build_report → calculate_average. Do not confuse it with: The list of every function defined in the project.
- Runtime State
- The values, active calls, and other program information that exist at a particular moment during execution. Example: score=95 while paused inside determine_rank(). Do not confuse it with: What you think the value should be based only on reading the source.