Skip to content
VS Code Debugging — Launch Configs, Breakpoints, Watch, Call Stack, Remote Debugging

VS Code Debugging — Launch Configs, Breakpoints, Watch, Call Stack, Remote Debugging

DodaTech Updated Jun 20, 2026 8 min read

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:

SectionPurpose
VariablesLocal, global, and closure variables
WatchCustom expressions evaluated on each break
Call StackStack frames of the paused thread
Loaded ScriptsAll source files loaded by the runtime
BreakpointsAll 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

VariableExpands 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

ActionShortcutDescription
ContinueF5Resume execution
Step OverF10Execute current line, go to next
Step IntoF11Enter the function on current line
Step OutShift+F11Complete current function, return to caller
RestartCtrl+Shift+F5Restart the debugging session
StopShift+F5Stop 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:

  1. Ctrl+Shift+P → “Remote-SSH: Connect to Host”
  2. Select or enter SSH host
  3. Open folder on remote machine
  4. 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:

  1. Set a breakpoint in the auth service when a token is validated
  2. Trace the request from the API gateway through to the auth service
  3. Watch the JWT payload at each step
  4. Use step-out to return to the caller quickly
  5. Add a logpoint that logs each request URL with timing

FAQ

Can I debug multiple languages in one session?
Yes — VS Code supports multi-target debugging. You can debug a Node.js backend and a Python worker simultaneously with different launch configurations in the same workspace.
Why don’t my breakpoints work in TypeScript?
Breakpoints need source maps. Ensure "sourceMap": true and "inlineSources": true in tsconfig.json, and set "outFiles" in launch.json to point to your compiled output.
How do I debug a child process?
Node.js child processes forked with child_process.fork() inherit the debug port. Use "autoAttachChildProcesses": true or start each child with a unique --inspect-port.
What’s the difference between launch and attach?
Launch starts the program under the debugger. Attach connects to an already-running process that’s listening on a debug port. Use launch for active development, attach for debugging processes started outside VS Code.
How do I debug memory leaks?
Use the built-in heap snapshot (Node.js) or a memory profiler extension. Set breakpoints before and after suspect operations, compare heap snapshots in the debug console with process.memoryUsage().
Can I debug remotely without SSH?
Yes — the debugpy library (Python) and --inspect (Node.js) can listen on network interfaces. For security, use SSH tunneling: ssh -L 9229:localhost:9229 remote-server.com.

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