Debugging in VS Code: Breakpoints, launch.json, Fixes
To debug code in VS Code, click to the left of a line number to set a breakpoint (or press F9), then press F5. When the program stops on that line, step through it with F10 and F11 and read the variables in the Run and Debug view.
That works for a single JavaScript, TypeScript or Python file with no setup. For anything bigger (a server with arguments and environment variables, a test suite, a process that’s already running) you write a launch.json. This guide covers both, plus the breakpoint types worth knowing and the fixes for the problems everyone hits. The official VS Code debugging docs have the full reference.
How to debug code in VS Code
- Install the debugger for your language. JavaScript, TypeScript and Node.js work out of the box. For Python, install Microsoft’s Python and Python Debugger extensions; for C#, the C# Dev Kit; for Java, the Extension Pack for Java.
- Set a breakpoint. Click in the gutter left of the line number, or put the cursor on the line and press F9. A red dot appears. Breakpoints live in the editor, not in your source file.
- Start debugging. Press F5, or open the Run and Debug view (Ctrl+Shift+D, Shift+Cmd+D on macOS) and click Run and Debug. If there’s no
launch.json, VS Code asks which debugger to use for the current file. - Inspect. When execution stops, the Run and Debug view shows Variables, Watch, Call Stack and Breakpoints. Hover over any variable in the editor to see its value, or type an expression in the Debug Console to evaluate it in the paused frame.
- Step with the toolbar or the keys below, and fix the code once you see where it goes wrong.
Debug toolbar and keyboard shortcuts
| Action | Key | What it does |
|---|---|---|
| Continue | F5 | Run to the next breakpoint |
| Pause | F6 | Pause a running program where it is |
| Step Over | F10 | Run the current line and stop on the next one in the same function |
| Step Into | F11 | Enter the function called on the current line |
| Step Out | Shift+F11 | Finish the current function and stop in its caller |
| Restart | Ctrl+Shift+F5 (Shift+Cmd+F5) | Stop and start the session again |
| Stop | Shift+F5 | End the session |
| Toggle Breakpoint | F9 | Add or remove a breakpoint on the current line |
Most of a session is Step Over. Use Step Into when the line calls a function you suspect.
Debug Node.js without a launch.json
Open the Command Palette (Ctrl+Shift+P, Cmd+Shift+P on macOS) and run Debug: JavaScript Debug Terminal. Any Node.js process you start in that terminal (npm run dev, node script.js, a test runner) runs with the debugger attached, and your breakpoints hit. It is often the fastest way to debug a script with a complicated start command.
Configuring launch.json
launch.json lives in your project’s .vscode folder and tells VS Code how to start or attach to your program. Create it from the Run and Debug view (create a launch.json file), or add to an existing one with Add Configuration…. Commit it, and everyone on the team gets the same F5.
Every configuration has a type (which debugger), a request (launch starts the program, attach connects to one that’s running) and a name that appears in the dropdown.
Node.js and TypeScript
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch server",
"program": "${workspaceFolder}/src/index.js",
"envFile": "${workspaceFolder}/.env"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Node on 9229",
"port": 9229
}
]
}
The attach configuration connects to a process you started yourself with the inspector on:
node --inspect src/index.js
For TypeScript compiled to dist/, turn on "sourceMap": true in tsconfig.json and add "outFiles": ["${workspaceFolder}/dist/**/*.js"] to the configuration, so breakpoints in .ts files map to the JavaScript that runs.
Python
{
"name": "Python: current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": { "APP_ENV": "development" },
"justMyCode": true
}
The Python Debugger extension uses "type": "debugpy"; older configurations that say "type": "python" are deprecated. Use env for a few variables or envFile to load a .env file.
To debug a script on another machine or in a container, start it under debugpy and attach:
python -m debugpy --listen 5678 --wait-for-client ./myscript.py
{
"name": "Python: attach",
"type": "debugpy",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 }
}
.NET
With the C# Dev Kit installed, F5 usually finds the project without any configuration. To control the program path, arguments or working directory, use a coreclr configuration:
{
"name": ".NET: launch console app",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net10.0/MyApp.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false
}
preLaunchTask runs the build task from .vscode/tasks.json first, so you never debug a stale build. Debug a Debug build: a Release build optimizes code away and breakpoints may not bind.
Keep launch.json portable
Never hardcode paths from your own machine. Use VS Code’s variables instead:
${workspaceFolder}: the root of the open folder${file}: the file open in the active editor${env:NAME}: an environment variable, such as${env:HOME}
Debug the front end and back end together
A compound starts several configurations with one F5, so you can set breakpoints in the browser code and the API in the same session:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch server",
"program": "${workspaceFolder}/server/index.js"
},
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/client"
}
],
"compounds": [
{
"name": "Server + client",
"configurations": ["Launch server", "Launch Chrome"],
"stopAll": true
}
]
}
stopAll ends both sessions when you stop either one.
Breakpoints beyond the red dot
Conditional breakpoints
A bug that appears on the 500th pass through a loop doesn’t need 499 presses of F5. Right-click the gutter, choose Add Conditional Breakpoint…, and pick one of:
- Expression: pause only when it’s true, such as
i === 499oruser.role === "admin". - Hit Count: pause after the line has run a given number of times, such as
500. - Wait for Breakpoint: stay inactive until another breakpoint has been hit (a triggered breakpoint).
Logpoints
A logpoint prints a message to the Debug Console instead of pausing. Right-click the gutter, choose Add Logpoint…, and write the message with expressions in curly braces:
Processing request for user {user.id}, cart size {cart.items.length}
Logpoints show as diamonds. They replace the console.log you would otherwise add, run and delete. They only exist while the debugger is attached, so they are for your machine, a staging box or a container you attach to, not a substitute for logging in production.
Other breakpoint types
- Inline breakpoints (Shift+F9) stop at a column, for lines with several statements or minified code.
- Function breakpoints stop when a named function is entered; add them with the + in the Breakpoints section.
- Data breakpoints stop when a value changes or is read. Right-click a variable in the Variables section and pick a Break on option; which options appear depends on the language’s debugger.
Watch expressions
The Variables section shows locals. To follow something derived, add it to Watch: items.length * price or config.user.isAdmin. VS Code re-evaluates every watch each time the program pauses. Keep the list short: watching a large object makes every step slower.
Debugging tests
You don’t need a separate configuration to debug a single test. Open the Testing view (the flask icon) or right-click the run icon next to a test in the editor and choose Debug Test. It works with pytest and unittest through the Python extension, Jest and Vitest through their extensions, and JUnit and .NET tests through the Java and C# extensions. Breakpoints in the test and in the code under test both hit.
When a coding agent does the fixing, the same idea applies: give it the stack trace and a command that reproduces the bug, and ask for a failing test before the fix. In one small team’s 866 agent-written pull requests, about half of those that named a task type were bug fixes; Zest shows which coding agents and models each engineer uses and the pull requests they lead to.
Remote machines, containers and WSL
- Remote - SSH: install the extension, run Remote-SSH: Connect to Host… and enter
user@hostname. VS Code installs its server on the remote machine, your extensions run there, and F5 debugs the remote process as if it were local. - Dev Containers: open a folder with a
.devcontainer/devcontainer.jsonand choose Reopen in Container, or run Dev Containers: Attach to Running Container…. The debugger runs inside the container, next to your code. - WSL: the WSL extension does the same for a Linux distribution on Windows.
- Container Tools: Microsoft’s Container Tools extension (formerly the Docker extension) builds and runs containers from VS Code and can add debug configurations for Node.js, Python and .NET apps.
To attach to a Node.js process in a container without Dev Containers, start it with node --inspect=0.0.0.0:9229, publish the port (-p 9229:9229), and map the paths:
{
"type": "node",
"request": "attach",
"name": "Attach to container",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
Only publish the inspector port on your own machine: anyone who can reach it can run code in the process.
Troubleshooting
Breakpoints are gray and not being hit
A hollow gray breakpoint means the debugger couldn’t bind it to code that runs. The usual causes:
- Source maps (JavaScript and TypeScript). The build doesn’t emit them, or
outFiles(Node) orwebRoot(browser) points to the wrong folder. - A stale or Release build. Rebuild, and for C# or C++ debug the Debug configuration.
- No code on that line. Blank lines, comments and some declarations can’t hold a breakpoint.
justMyCode(Python). It defaults totrue, so breakpoints inside installed libraries are skipped. Set"justMyCode": falseto step into them.- The wrong interpreter or runtime. In Python, run Python: Select Interpreter and pick the environment your code actually uses.
Common problems and fixes
| Problem | Usual cause | Fix |
|---|---|---|
| Breakpoints gray | Missing or misplaced source maps; old build | Turn on source maps, fix outFiles or webRoot, rebuild |
| Can’t step into library code | justMyCode is on (Python) |
Set "justMyCode": false |
| Attach fails | Process not started with the debug flag, or wrong port | Start with --inspect (Node) or debugpy --listen (Python) and match the port |
| Stepping is slow | Large objects in Watch, or busy conditional breakpoints in a hot loop | Trim the Watch list; use a hit count instead of an expression |
| F5 runs the wrong thing | Another configuration is selected | Pick the right one in the Run and Debug dropdown |
Frequently asked questions
Do I need a launch.json to debug in VS Code?
No. For a single JavaScript, TypeScript or Python file, press F5 and pick a debugger. For Node.js, the JavaScript Debug Terminal attaches to anything you run in it. Write a launch.json when you need arguments, environment variables, an attach target or a setup the whole team shares.
What’s the difference between launch and attach?
launch starts the program for you under the debugger. attach connects to a program that’s already running and was started with its debug flag, such as node --inspect or python -m debugpy --listen. Launch what you start yourself; attach to servers, containers and remote processes.
Can I debug the front end and back end at the same time?
Yes. Define a configuration for each, then a compounds entry that lists both. One F5 starts them together, and breakpoints work in both.
How do I debug an app inside a Docker container?
Either open the project in the container with the Dev Containers extension and debug as usual, or start the app with its debug port published and use an attach configuration with localRoot and remoteRoot mapped.
Why are my breakpoints grayed out and “unverified”?
VS Code can’t match the line to code that’s running. For JavaScript and TypeScript, check source maps and outFiles or webRoot. For compiled languages, rebuild in Debug mode. For Python, check justMyCode and the selected interpreter.