VS Code Debugging — Launch Configs, Breakpoints, Watch, Call Stack, Remote Debugging
VS Code’s built-in debugger is one of its most powerful features, supporting Node.js, Python, Java, Go, Rust, and dozens of other languages through extensions. This guide covers everything from basic breakpoints to remote debugging in production-like environments.
What You’ll Learn
You’ll configure launch.json for multi-target debugging, use conditional and logpoint breakpoints effectively, inspect variables with watch expressions, navigate the call stack, and debug applications running in Docker containers or on remote SSH servers.
Why VS Code Debugging Matters
The debugger is the single most effective tool for understanding code behavior. Instead of adding console.log statements and re-running, a debugger lets you pause execution, inspect state, and step through code line by line. VS Code’s debugger works across languages and environments, providing a consistent experience whether you’re debugging a Node.js API, a Python script, or a Go microservice.
Learning Path
flowchart LR
A[VS Code Basics] --> B[Debugging Fundamentals<br/>You are here]
B --> C[Launch Configs]
C --> D[Remote Debugging]
style B fill:#f90,color:#fff
Debugger UI Overview
The debugger panel (Ctrl+Shift+D) has five sections:
| Section | Purpose |
|---|---|
| Variables | Local, global, and closure variables |
| Watch | Custom expressions evaluated on each break |
| Call Stack | Stack frames of the paused thread |
| Loaded Scripts | All source files loaded by the runtime |
| Breakpoints | All breakpoints, enabled/disabled |
Launch Configurations
Launch configurations are stored in .vscode/launch.json:
Node.js
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/src/index.js",
"skipFiles": ["<node_internals>/**"],
"env": {
"NODE_ENV": "development",
"PORT": "3000"
},
"console": "integratedTerminal"
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229,
"restart": true
}
]
}Python
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"env": {"PYTHONPATH": "${workspaceFolder}"}
},
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver", "0.0.0.0:8000"],
"django": true
}
]
}Compound Launches (Multiple Processes)
{
"version": "0.2.0",
"configurations": [
{
"name": "API Server",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/api/server.js",
"port": 9229
},
{
"name": "Worker",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/worker/index.js",
"port": 9230
}
],
"compounds": [
{
"name": "Full Stack",
"configurations": ["API Server", "Worker"]
}
]
}Launch Configuration Variables
| Variable | Expands to |
|---|---|
${workspaceFolder} | Root workspace path |
${file} | Currently open file |
${fileBasename} | Current filename |
${fileDirname} | Directory of current file |
${env:VAR} | Environment variable |
${command:pickProcess} | Select process at runtime |
Breakpoints
Regular Breakpoints
Click the gutter (left of line numbers) or press F9 to toggle a breakpoint.
Conditional Breakpoints
Right-click the gutter → “Add Conditional Breakpoint”:
// Break only when order total exceeds 1000
order.total > 1000
// Break only on the 10th call
count === 10
// Break when variable changes
order.status !== 'pending'Logpoints (Tracepoints)
Logpoints print a message to the debug console without pausing execution:
// Right-click → "Add Logpoint"
// Message: "Order ${order.id} processed in ${duration}ms"
Use logpoints when:
- You can’t modify the source code
- The code runs in a hot loop (breakpoints would be too slow)
- You need to trace execution flow across multiple files
Function Breakpoints
Break when a specific function is called, without setting a breakpoint in the file:
// Break on any method named "validate"
// In the Breakpoints section → + → type: "validate"Watch Expressions
Evaluate arbitrary expressions at each breakpoint:
// Common watch expressions
order.items.length // Array length
JSON.stringify(order, null, 2) // Full object view
typeof result // Type check
process.memoryUsage().heapUsed // Memory usage
new Date().toISOString() // Timestamp
Watch expressions are re-evaluated every time execution pauses. Avoid expensive operations (large loops, file I/O) in watches.
Call Stack Navigation
When execution pauses, the call stack shows the chain of function calls:
Call Stack
api/src/controllers/orderController.js:45 OrderController.create()
api/src/services/orderService.js:123 OrderService.processPayment()
api/src/services/paymentGateway.js:89 PaymentGateway.charge()
node:internal/process/task_queues.js:95 processTicksAndRejections()Stack Actions
| Action | Shortcut | Description |
|---|---|---|
| Continue | F5 | Resume execution |
| Step Over | F10 | Execute current line, go to next |
| Step Into | F11 | Enter the function on current line |
| Step Out | Shift+F11 | Complete current function, return to caller |
| Restart | Ctrl+Shift+F5 | Restart the debugging session |
| Stop | Shift+F5 | Stop debugging |
Remote Debugging
Docker Container
{
"name": "Docker: Attach to Node",
"type": "node",
"request": "attach",
"port": 9229,
"address": "localhost",
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app",
"restart": true,
"skipFiles": ["<node_internals>/**"]
}In your Dockerfile, ensure the debug port is exposed:
FROM node:20
EXPOSE 9229
CMD ["node", "--inspect=0.0.0.0:9229", "src/index.js"]SSH Remote Development
With the Remote - SSH extension:
Ctrl+Shift+P→ “Remote-SSH: Connect to Host”- Select or enter SSH host
- Open folder on remote machine
- Set breakpoints and debug as if local — VS Code handles the transport
Python Remote Debugging (debugpy)
# Install debugpy on the remote machine
pip install debugpy
# Start the debug server
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client app.py{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": {
"host": "remote-server.com",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/opt/app"
}
]
}Debugging Specific Scenarios
Node.js Async Debugging
async function fetchData() {
const response = await fetch('https://api.example.com/data');
// Break here — Step Into continues through microtask queue
const data = await response.json();
return data;
}VS Code handles async/await transparently. F11 steps into async functions correctly, navigating through the microtask queue.
Debugging Tests
{
"name": "Jest: Current File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"${fileBasename}",
"--runInBand",
"--no-cache"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}Debugging Cron Jobs and Short-Lived Processes
{
"name": "Attach by Process ID",
"type": "node",
"request": "attach",
"processId": "${command:PickProcess}",
"restart": true,
"timeout": 30000
}Common Debugging Mistakes
1. Debugging Optimized Code
Production builds often minify or transform code. Debug the development build or use source maps. Set "sourceMaps": true in launch.json.
2. Breakpoints Not Hitting
Common causes:
- File path doesn’t match between source and runtime (
"localRoot"/"remoteRoot"mismatch) - Code hasn’t been loaded yet (try
"restart": true) - Source maps not configured
- Breakpoint set on an empty line or comment
3. Debugging in Production
Never attach a debugger to a production environment. Use logpoints (no pausing) or structured logging in production. Debug on staging that mirrors production.
4. Not Using Logpoints
For performance-critical or high-traffic code, logpoints are 100x faster than breakpoints because they don’t pause the event loop. They write to the debug console without blocking.
5. Overusing Watch Expressions
Each watch runs on every breakpoint pause. Too many watches (especially with network calls or DOM queries) slow debugging to a crawl. Delete watches you’re not actively using.
6. Ignoring the Debug Console
The debug console (Ctrl+Shift+Y) lets you type arbitrary expressions in the context of the paused frame. You can call functions, modify variables, and test hypotheses without restarting.
7. Forgetting to Remove Breakpoints
Leftover breakpoints in committed code can cause confusion for teammates. Use the Breakpoints panel to disable/enable groups. Export/import breakpoints with the project.
Practice Questions
1. What’s the difference between a breakpoint and a logpoint? A breakpoint pauses execution. A logpoint prints a message to the debug console without pausing. Use breakpoints to inspect state; use logpoints for tracing without disruption.
2. How do you debug a Node.js application running in Docker?
Expose the debug port (9229) in Docker, start node with --inspect=0.0.0.0:9229, and create an attach configuration pointing to localhost:9229 with localRoot and remoteRoot mappings.
3. What’s the purpose of the Call Stack panel? It shows the chain of function calls that led to the current breakpoint. You can click any frame to inspect its local variables and see how execution reached the current point.
4. How do you evaluate arbitrary expressions during debugging?
Use the Debug Console (Ctrl+Shift+Y). Type any expression — it runs in the context of the currently selected stack frame. You can modify variables, call functions, and test conditions.
5. Challenge: A function crashes intermittently with a null reference error. You can’t reproduce it locally. How would you use debugging tools to identify the root cause? Answer: (1) Add a conditional breakpoint that triggers when the suspect variable is null. (2) Add a logpoint that logs all arguments on each call. (3) Use the call stack to trace which caller passes null. (4) If the crash is rare, use a conditional breakpoint with a pass count or a watch that checks for null.
Mini Project: Debug a Multi-Service Application
Create a launch configuration for debugging a three-service application:
{
"version": "0.2.0",
"configurations": [
{
"name": "Auth Service",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/auth/src/server.js",
"env": { "PORT": "3001", "DB_URL": "mongodb://localhost:27017/auth" },
"outFiles": ["${workspaceFolder}/auth/dist/**/*.js"]
},
{
"name": "API Gateway",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/gateway/src/server.js",
"env": { "PORT": "3000" }
},
{
"name": "Worker Service",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/worker/src/index.js",
"env": { "REDIS_URL": "redis://localhost:6379" }
}
],
"compounds": [
{
"name": "All Services",
"configurations": ["Auth Service", "API Gateway", "Worker Service"],
"preLaunchTask": "Build All"
}
]
}Practice debugging:
- Set a breakpoint in the auth service when a token is validated
- Trace the request from the API gateway through to the auth service
- Watch the JWT payload at each step
- Use step-out to return to the caller quickly
- Add a logpoint that logs each request URL with timing
FAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro