When a Python program runs but produces the wrong result, adding more print() calls can quickly become confusing. A debugger lets you pause the program, inspect values, execute one line at a time, and understand exactly how the code reached its current state. This guide shows how to debug Python in VS Code using the current Microsoft Python debugging tools.
You will learn how to select the correct interpreter, set breakpoints, start a debugging session, use Step Over and Step Into, inspect variables and the call stack, create conditional breakpoints and logpoints, configure launch.json, and diagnose common problems.
For terminal-based debugging without an editor, see the Python pdb guide. Automated tests also reduce the amount of manual debugging required; the Python unit testing guide explains that workflow.
What You Need
Install Visual Studio Code, Python, and the Microsoft Python extension. The Python Debugger extension provides the debug adapter used by current VS Code Python sessions and is normally installed as a dependency of the Python extension.
The official VS Code Python debugging documentation describes the available configurations and controls. The Microsoft Python extension page lists its editor, environment, testing, and debugging integrations.
Open a Project Folder
Open the folder containing your Python files rather than opening only one file. A workspace folder gives VS Code a stable location for settings, virtual environments, and the .vscode/launch.json configuration.
Create a file named shopping_cart.py:
def calculate_total(prices, discount=0):
subtotal = sum(prices)
discount_value = subtotal * discount
return subtotal - discount_value
items = [19.90, 5.50, 12.00]
final_total = calculate_total(items, discount=10)
print(f"Total: ${final_total:.2f}")The code contains a logic error: the function expects a decimal discount such as 0.10, but the caller passes 10. The program is syntactically valid, so Python runs it and prints an incorrect negative total.
Select the Correct Python Interpreter
Use the interpreter indicator in the VS Code status bar or run Python: Select Interpreter from the Command Palette. Choose the environment where your packages are installed.
Using the wrong interpreter can cause missing-package errors, an unexpected Python version, or different environment variables. When a project uses a virtual environment, select that environment before debugging.
Set Your First Breakpoint
Click in the gutter to the left of the line number beside:
discount_value = subtotal * discountA red dot marks the breakpoint. When execution reaches that line, the debugger pauses before running it. You can also toggle a breakpoint on the current line with F9.
Start a Debugging Session
Open shopping_cart.py, then use Run and Debug or press F5. For a simple file, choose the Python file configuration when prompted.
When the breakpoint is reached, VS Code highlights the current line and opens the debugging interface. The left sidebar normally displays Variables, Watch, Call Stack, and Breakpoints. The Debug Console accepts expressions evaluated in the paused context.
Inspect Variables
At the breakpoint, inspect:
prices: the input list.discount: currently10.subtotal: the sum of the prices.
The unexpected discount value identifies the bug. Change the function call to:
final_total = calculate_total(items, discount=0.10)Variable inspection is especially useful when values depend on several branches or loops. The Python variable scope guide explains why local, enclosing, global, and built-in names may appear in different frames.
Use the Main Debug Controls
| Control | Typical shortcut | What it does |
|---|---|---|
| Continue | F5 | Runs until the next breakpoint or program end. |
| Step Over | F10 | Executes the current line without entering called functions. |
| Step Into | F11 | Enters a function called on the current line. |
| Step Out | Shift + F11 | Finishes the current function and returns to its caller. |
| Restart | Toolbar | Starts the session again. |
| Stop | Shift + F5 | Ends the debugging session. |
Shortcuts can vary by operating system and keymap. The toolbar icons are always available during a session.
Understand Step Over vs. Step Into
Consider this program:
def apply_tax(amount, rate):
return amount * (1 + rate)
def create_invoice(prices):
subtotal = sum(prices)
total = apply_tax(subtotal, 0.08)
return total
print(create_invoice([10, 20, 30]))When paused on the line calling apply_tax(), Step Over calculates the result and remains in create_invoice(). Step Into opens apply_tax() so you can inspect amount and rate.
Use the Watch Panel
A watch expression is recalculated whenever execution pauses. Useful expressions include:
subtotal * discount
len(prices)
subtotal - discount_value
all(price >= 0 for price in prices)Watch expressions are helpful when the value you care about is not stored in a variable. Avoid expressions with side effects because evaluating them can change program state.
Use the Debug Console
While paused, evaluate Python expressions in the Debug Console:
subtotal
prices[0]
type(discount)
subtotal * 0.10The console is an inspection tool, not a replacement for permanent fixes. Changes made during a session disappear when the program restarts.
Read the Call Stack
The call stack shows how execution reached the current line. In a nested program, it may display:
main
create_report
calculate_average
validate_valuesSelecting a frame changes the local variables shown for that function. This is valuable when an error occurs far from the original caller or when the same function is used from several places.
Create Conditional Breakpoints
A normal breakpoint pauses every time. In a loop with thousands of iterations, add a condition so it pauses only for a specific state.
orders = [15, 42, -8, 27, 100]
for index, amount in enumerate(orders):
processed = amount * 1.1
print(index, processed)Right-click the breakpoint and choose Edit Breakpoint. Example conditions:
amount < 0
index == 3
processed > 50Conditional breakpoints are ideal for invalid records, rare states, and later loop iterations.
Use Hit Counts
A hit-count breakpoint pauses after the line has been reached a chosen number of times. This is useful when a problem appears only after repeated calls. Depending on the debug interface, you can set a comparison such as pausing when the hit count equals or exceeds a value.
Use Logpoints Without Changing Code
A logpoint writes a message to the Debug Console instead of pausing. It is useful when you want temporary diagnostic output but do not want to add and later remove print() calls.
Example log message:
Processing order {index}: amount={amount}For persistent application diagnostics, use the Python logging module. Logpoints are editor configuration; logging remains in the application and can write to files or monitoring systems.
Debug Exceptions
VS Code can pause when exceptions are raised. Open the Breakpoints section and enable the relevant exception options. This lets you inspect variables at the point of failure rather than only reading the final traceback.
def average(values):
return sum(values) / len(values)
print(average([]))Pausing on the resulting ZeroDivisionError shows that values is empty. The traceback and call stack then explain where the empty list originated.
A SyntaxError happens before normal execution can begin, so breakpoints cannot fix invalid syntax. Use the Python SyntaxError guide to read those messages.
Create a launch.json Configuration
Simple files can be debugged without configuration. Create .vscode/launch.json when you need arguments, a module, environment variables, a different working directory, or another launch behavior.
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug current Python file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
}
]
}program selects the file, console chooses where input and output appear, and justMyCode controls whether stepping normally skips library internals.
Pass command-line arguments
{
"name": "Debug report command",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/report.py",
"args": ["sales.csv", "--format", "json"],
"console": "integratedTerminal"
}This pattern is useful for the Python CLI with argparse created later in this batch.
Add non-secret environment values
{
"env": {
"APP_ENV": "development"
}
}Do not commit passwords or API tokens inside launch.json. Use environment management appropriate to the project and keep secrets outside version control.
Debug Tests
The Testing view can discover and run unittest or pytest tests. Set a breakpoint inside the application or test, then use the debug action beside a test. This combines repeatable test inputs with interactive variable inspection.
The dedicated Pytest beginner guide explains assertions, exception tests, organization, and useful commands.
Common Debugging Problems
The breakpoint is gray or unverified
Confirm that the file being executed is the file containing the breakpoint, save the file, and verify the selected interpreter and launch configuration.
VS Code cannot import a package
Select the environment where the package is installed. Check the interpreter path in the status bar and compare it with the terminal command used for installation.
Input does not work
Use the integrated terminal for programs that call input(). A Debug Console is not always the correct place for interactive terminal input.
The debugger enters library code
Keep justMyCode enabled for ordinary application debugging. Disable it only when you intentionally need to inspect a dependency.
The program behaves differently in debugging
Check the working directory, environment variables, arguments, and interpreter. A launch configuration may differ from the command you normally run in a terminal.
A Practical Debugging Routine
- Reproduce the problem with the smallest reliable input.
- Read the complete error or describe the incorrect result.
- Place a breakpoint before the state first becomes suspicious.
- Inspect input values and assumptions.
- Step through one decision at a time.
- Use the call stack to find the original caller.
- Fix the cause rather than only the final symptom.
- Add a test that fails before the fix and passes afterward.
- Remove temporary breakpoints and diagnostic output.
Frequently Asked Questions
What is the difference between Run and Debug?
Run executes the program normally. Debug starts it under a debugger so breakpoints, stepping, variable inspection, and exception controls are available.
Can I debug a virtual environment?
Yes. Select its interpreter before starting the session.
Can I change a variable while paused?
The debugger may allow evaluation or assignment in the Debug Console. Treat this as temporary experimentation; update the source code for the permanent fix.
Can I debug imported modules?
Yes. Set breakpoints in project modules. To step into external libraries, adjust justMyCode.
Should I stop using print()?
No. Simple output remains useful. A debugger is better when you need to examine changing state, nested calls, or a precise execution path.
Conclusion
To debug Python in VS Code effectively, start with the correct interpreter, pause before the suspected failure, inspect values, and use stepping and the call stack to follow the program’s decisions. Conditional breakpoints and logpoints make large loops easier to investigate, while launch.json makes complex runs repeatable.
The strongest workflow combines debugging with clear error reading, logging, and automated tests. A debugger explains one execution; a regression test helps prevent the same bug from returning.






