Most developers debug by inserting console.log, running the program, checking output, removing the log, and repeating. VS Code has a full interactive debugger that makes this workflow obsolete. In 2026, with AI assistance integrated directly into the debug cycle, knowing the debugger saves hours every week. Here is how to use it properly.
What changed in 2026
- GitHub Copilot in VS Code can now analyze a stack trace in the Debug Console and suggest a fix inline — the "Explain this error" button appears directly in the debug panel.
- JavaScript Debug Terminal (built-in since VS Code 1.6x) auto-attaches to any Node process started in the integrated terminal — no
launch.json needed for basic cases.
- Remote debugging over SSH is built into VS Code's remote extension; attaching to a process on a remote server now takes two clicks.
- Inline variable values are displayed in the editor next to each line when paused, making the Variables panel optional for simple inspections.
Setting up launch.json
Press F5 or click Run → Add Configuration. For a Node/TypeScript project:
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Node",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/index.ts",
"runtimeArgs": ["-r", "ts-node/register"],
"env": { "NODE_ENV": "development" },
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
},
{
"name": "Attach to Process",
"type": "node",
"request": "attach",
"port": 9229,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
}
]
}
Commit .vscode/launch.json so every team member gets the same debug config.
Starting Node with the inspector
# Start with debugger listener
node --inspect src/index.js
# Break on first line (useful for short scripts)
node --inspect-brk src/index.js
Then attach using the "Attach to Process" config above, or use VS Code's auto-attach feature (search "Auto Attach" in Command Palette and set to "Smart").
Breakpoint types
| Type |
How to set |
Use case |
| Line breakpoint |
Click gutter |
Pause at a specific line |
| Conditional breakpoint |
Right-click gutter → "Add Conditional" |
Pause only when expression is true |
| Hit count breakpoint |
Right-click → "Add Hit Count" |
Pause after N hits |
| Logpoint |
Right-click → "Add Logpoint" |
Print message without pausing |
| Exception breakpoint |
Run panel → Breakpoints section |
Pause on any thrown error |
Conditional breakpoint example — break only when an array has a specific user ID:
users.some(u => u.id === 'abc-123')
Logpoints
A logpoint prints to the Debug Console without stopping execution and without modifying source:
Right-click a gutter → "Add Logpoint" → enter: User object: {JSON.stringify(user)}
This is the replacement for console.log during debugging. Remove all logpoints at once with "Remove All Breakpoints" in the Run menu.
The Watch panel
Add expressions to Watch to evaluate them continuously while stepping:
users.length
response.status
this.cache.size
This is faster than hovering over variables and more persistent than the Variables panel.
Debugging inside Docker
In docker-compose.yml, expose port 9229:
services:
api:
command: node --inspect=0.0.0.0:9229 dist/index.js
ports:
- "9229:9229"
In launch.json, add a remote attach config:
{
"name": "Attach to Docker",
"type": "node",
"request": "attach",
"address": "localhost",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
"sourceMaps": true
}
Debugging React / Next.js
For browser-based React debugging, use the built-in browser debugger:
{
"name": "Launch Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMaps": true
}
Or use the "JavaScript Debug Terminal" and start npm run dev inside it — VS Code auto-attaches to Node processes.
How to pick your approach
- Short script or one-off? → JavaScript Debug Terminal — no config needed.
- Team project? → Commit a
launch.json with named configs for all common scenarios.
- Running in Docker? → Expose port 9229, use attach mode with
localRoot/remoteRoot mapping.
- Production bug? → Enable
--inspect on a staging instance, use remote SSH debugging.
Common mistakes
No source maps. Without sourceMaps: true and correct outFiles, breakpoints in TypeScript source do not hit because the debugger sees compiled JS. Always set both.
Forgetting --inspect in Docker. The default node dist/index.js command does not enable the inspector. Add --inspect=0.0.0.0:9229 to the Docker command.
Not committing launch.json. Every developer recreates configs independently. Commit the file.
Leaving breakpoints in code. debugger; statements committed to source break in production if the inspector is accidentally enabled. Use VS Code breakpoints, which are never in source.
What to skip
- Debugging with
console.log in a loop. A conditional breakpoint is faster and does not produce thousands of log lines.
- The
debugger; statement in committed code. It has its uses in development but should never be committed.
- Restarting the process for each debug iteration. The watch + hot reload (
ts-node-dev, nodemon) combined with attach mode lets you debug without restarting.
FAQ
Does VS Code debug Python, Go, and other languages?
Yes. Install the language-specific debug extension (Python, Go, C/C++). Each adds a debugger type to launch.json.
How do I share debug configs across a team?
Commit .vscode/launch.json. Add .vscode/settings.json to .gitignore for personal settings, but keep launch.json tracked.
Can I debug Jest tests?
Yes. Add a config with "program": "${workspaceFolder}/node_modules/.bin/jest" and "args": ["--runInBand"]. The --runInBand flag prevents Jest from using worker threads, which do not support the inspector.
What is the difference between launch and attach?
Launch starts a new process with the debugger attached from the start. Attach connects to an already-running process. Use launch for development, attach for Docker and remote servers.
Where to go next