Most developers discover the VS Code debugger, try it briefly, and fall back to
console.log(). The debugger can seem slower to set up than adding a log statement, but that calculus reverses quickly once a few features are familiar. Conditional breakpoints, logpoints, inline variable values, and launch configurations remove most of the setup friction and provide significantly more information than logs alone.This guide covers the debugger features that change how debugging feels in practice, not just theoretically.
What this covers:
The Debugger panel and how to navigate it
Conditional breakpoints, logpoints, and hit count breakpoints
Inline variable values during a debugging session
Debugging asynchronous code
Launch configurations with
launch.jsonRemote debugging in Docker or over SSH
Useful debugging extensions
1. The Debugger Panel
The entry point is the Run and Debug view, opened with Ctrl+Shift+D on Windows/Linux or Cmd+Shift+D on macOS. For a Node.js project, clicking "Run and Debug" and selecting Node.js starts the application in debug mode immediately without any configuration.
Once running, the panel provides:
Variables — the current scope's variables with their values, expandable for objects and arrays
Watch — custom expressions evaluated continuously as execution steps. An expression like
user?.profile?.emailevaluates the full path without manually expanding nested objects on every pauseCall Stack — the full call chain leading to the current position, clickable to jump to any frame
Breakpoints — a list of all active breakpoints, each individually toggleable
The step controls at the top of the editor — Continue, Step Over, Step Into, Step Out — work on the current execution position. Step Into follows a function call into its body. Step Out runs the rest of the current function and pauses when it returns. Step Over executes the current line without descending into function calls.
2. Breakpoint Types
A basic breakpoint pauses execution every time a line is reached. Three additional breakpoint types provide more precize control.
Conditional breakpoints pause only when an expression evaluates to true. Right-click an existing breakpoint and select "Edit Breakpoint", then enter the condition:
user.id === 42In a loop or a frequently-called function, this avoids stepping through hundreds of iterations to reach the specific case that is failing. The condition has access to all variables in scope at that line.
Logpoints write a message to the debug console without pausing execution and without modifying the source code. Right-click the gutter where a breakpoint would go and select "Add Logpoint":
User login event: {user.id}, role: {user.role}Values in curly braces are evaluated and interpolated. Logpoints are particularly useful in hot paths where pausing execution would alter the timing-sensitive behavior being investigated.
Hit count breakpoints pause after a line has been executed a specified number of times. Set a hit count condition with a number (pause on the Nth hit) or an expression like % 10 (pause every 10th hit). Useful when a bug manifests after many iterations of a loop.
3. Inline Variable Values
During a debugging session, VS Code can display variable values directly in the editor next to the code that sets or uses them, rather than requiring a hover or a glance at the Variables panel.
Enable it in Settings (Ctrl+, or Cmd+,), search for debug.inlineValues, and set it to on or auto. With this enabled, stepping through code shows the current value of each variable inline, which makes tracking state changes through a sequence of steps considerably faster.
4. Debugging Asynchronous Code
async/await code is straightforward to debug in VS Code. Set a breakpoint anywhere inside an async function and the debugger pauses at that line when execution reaches it, with the awaited value already resolved:
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`); // set breakpoint here
return res.json();
}Pausing after the await shows res with the resolved Response object. The resolved value is inspectable in the Variables panel.
For debugging Promize chains and complex async flows, enable Async Call Stacks in the debugger settings. Without it, the call stack shows only the current microtask queue entry. With it enabled, VS Code reconstructs the logical call chain across async boundaries, showing the sequence of async operations that led to the current position.
When a breakpoint inside an async function is not being hit, the most common cause is the Promize being created but not awaited by the caller. Check the Call Stack panel to verify execution is actually reaching the function.
5. Launch Configurations
Running the application manually before attaching the debugger works for simple cases. For projects with specific startup arguments, environment variables, or multiple debug targets, a launch.json file defines reusable configurations that start with a single F5.
Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch App",
"program": "${workspaceFolder}/src/index.js",
"envFile": "${workspaceFolder}/.env",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "launch",
"name": "Run Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal"
}
]
}The skipFiles option tells the debugger to skip stepping into Node.js internals, which would otherwize appear in the call stack when stepping through built-in modules. The envFile option loads a .env file into the debug session's environment, removing the need to set variables manually.
Multiple configurations appear in the dropdown at the top of the Run and Debug panel. A project with separate configurations for the dev server, tests, and a specific script can switch between them without changing any settings.
6. Remote Debugging
When the application runs inside Docker or on a remote server, the VS Code debugger can attach to it directly using the Remote Development extension pack.
Install the extension pack and connect to the target:
Remote - SSH for servers accessible over SSH
Dev Containers for Docker containers
Once connected, VS Code opens a remote workspace where the Debugger panel, breakpoints, and Variables panel all work as if the code were local. The source files displayed are the remote files, so edits and breakpoints apply directly.
For Node.js running in a Docker container, expose the debug port in the container configuration:
# docker-compose.yml
app:
command: node --inspect=0.0.0.0:9229 src/index.js
ports:
- "3000:3000"
- "9229:9229"Then add an attach configuration to launch.json:
{
"type": "node",
"request": "attach",
"name": "Attach to Docker",
"port": 9229,
"remoteRoot": "/app",
"localRoot": "${workspaceFolder}"
}The remoteRoot and localRoot map the container's file paths to the local workspace so breakpoints set in local files correspond correctly to the remote code.
7. Useful Extensions
A few extensions extend debugging capabilities meaningfully:
Error Lens displays error and warning messages inline next to the affected code rather than requiring a hover or a check of the Problems panel. For TypeScript and linting errors, this provides immediate feedback without breaking the flow of reading code.
REST Client (or Thunder Client) allows API requests to be sent directly from a .http file in VS Code. Combined with a running debugger, it makes it straightforward to trigger a specific endpoint and step through the server-side handler without switching to an external tool.
JavaScript Debugger Companion extends browser debugging support for projects that need to debug client-side code directly from VS Code.
Key Takeaways
The Watch panel evaluates custom expressions continuously during a session. Use it to track nested values without manually expanding object trees on each pause.
Conditional breakpoints and logpoints replace most common uses of
console.log()without modifying the source code.debug.inlineValuesshows variable values next to the code during stepping, which is faster than switching to the Variables panel.Async call stacks reconstruct the logical chain of async operations, which is essential for debugging complex Promize flows.
launch.jsondefines reusable debug configurations with environment variables, skip files, and startup arguments. Multiple configurations for different targets appear in the Run and Debug dropdown.Remote debugging with the Dev Containers or Remote SSH extension provides a full debugging session against Docker or server-hosted code.
Conclusion
The VS Code debugger is substantially more capable than most developers use it for. The features described here — conditional breakpoints, logpoints, inline values, async call stacks, and launch configurations — replace the majority of console.log() workflows with more targeted, less intrusive alternatives that preserve execution context.
The setup cost for any individual feature is a few minutes. The time saved across a project is considerably more.
A specific VS Code debugging feature that has changed how you work? Share it in the comments.




