Skip to main content
XR / Virtual Reality

Vscode Learning Pathway

13 Modules
Chapter 7: Debugging Mastery54%

Debugging Mastery

Learning Objectives

  • Master VS Code's integrated debugger for multiple languages
  • Set up debug configurations for various scenarios
  • Use breakpoints, watch expressions, and call stacks effectively
  • Debug remotely (SSH, containers, cloud)
  • Implement advanced debugging techniques
  • Troubleshoot common debugging issues

Core Debugging Concepts

The VS Code Debugger Architecture

Debugging in VS Code: the debug view groups toolbar, variables, watch, call stack and breakpoints; debug adapters connect it to Node.js, Python, browsers and native code

Quick Start: Debugging Basics

Starting a Debug Session

Method 1: Auto-Configuration

1. Open file to debug
2. Press F5
3. Select environment (Node.js, Python, etc.)
4. VS Code creates launch.json
5. Debugging starts

Method 2: Manual Configuration

1. Run & Debug view (Ctrl+Shift+D)
2. "create a launch.json file"
3. Select environment
4. Customise configuration

Essential Keyboard Shortcuts

ActionShortcutPurpose
Start/ContinueF5Start or continue debugging
Step OverF10Execute current line, step over functions
Step IntoF11Step into function calls
Step OutShift+F11Step out of current function
RestartCtrl+Shift+F5Restart debug session
StopShift+F5Stop debugging
Toggle BreakpointF9Add/remove breakpoint on current line

Breakpoints

Types of Breakpoints

1. Line Breakpoints

Click in gutter (left of line numbers)
Or: Press F9 on line
Red dot appears = breakpoint active

2. Conditional Breakpoints

// Break only when condition is true
// Right-click breakpoint → Edit Breakpoint → Expression

for (let i = 0; i < 100; i++) {
  processItem(i); // Breakpoint: i === 50
}

// Breaks only when i is 50

3. Logpoints

// Log message without stopping
// Right-click gutter → Add Logpoint

function calculateTotal(items) {
  // Logpoint: "Total items: {items.length}"
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Logs to Debug Console without breaking

4. Hit Count Breakpoints

Right-click breakpoint → Edit Breakpoint → Hit Count

Example: "= 10" (break on 10th hit)
         "> 5" (break after 5th hit)
         "% 3" (break every 3rd hit)

Launch Configurations

JavaScript/TypeScript (Node.js)

Basic Node.js:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "skipFiles": ["<node_internals>/**"],
      "program": "${workspaceFolder}/src/index.js"
    }
  ]
}

With TypeScript:

{
  "type": "node",
  "request": "launch",
  "name": "Launch TypeScript",
  "preLaunchTask": "npm: build",
  "program": "${workspaceFolder}/src/index.ts",
  "outFiles": ["${workspaceFolder}/dist/**/*.js"],
  "sourceMaps": true
}

Attach to Running Process:

{
  "type": "node",
  "request": "attach",
  "name": "Attach to Process",
  "processId": "${command:PickProcess}",
  "skipFiles": ["<node_internals>/**"]
}

Debug Jest Tests:

{
  "type": "node",
  "request": "launch",
  "name": "Jest Current File",
  "program": "${workspaceFolder}/node_modules/.bin/jest",
  "args": ["${fileBasename}", "--config", "jest.config.js"],
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}

Python

Basic Python:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal",
      "justMyCode": true
    }
  ]
}

Flask Application:

{
  "name": "Python: Flask",
  "type": "python",
  "request": "launch",
  "module": "flask",
  "env": {
    "FLASK_APP": "app.py",
    "FLASK_DEBUG": "1"
  },
  "args": ["run", "--no-debugger", "--no-reload"],
  "jinja": true
}

Django:

{
  "name": "Python: Django",
  "type": "python",
  "request": "launch",
  "program": "${workspaceFolder}/manage.py",
  "args": ["runserver"],
  "django": true,
  "justMyCode": true
}

Pytest:

{
  "name": "Python: Pytest Current File",
  "type": "python",
  "request": "launch",
  "module": "pytest",
  "args": ["${file}", "-v"],
  "console": "integratedTerminal"
}

Browser (Chrome/Edge)

Launch Chrome:

{
  "type": "chrome",
  "request": "launch",
  "name": "Launch Chrome",
  "url": "http://localhost:3000",
  "webRoot": "${workspaceFolder}/src",
  "sourceMaps": true
}

Attach to Chrome:

{
  "type": "chrome",
  "request": "attach",
  "name": "Attach to Chrome",
  "port": 9222,
  "webRoot": "${workspaceFolder}"
}

Compound Configurations

Debug Full-Stack (Frontend + Backend):

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Backend",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/server/index.js"
    },
    {
      "name": "Frontend",
      "type": "chrome",
      "request": "launch",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}/client"
    }
  ],
  "compounds": [
    {
      "name": "Full Stack",
      "configurations": ["Backend", "Frontend"],
      "stopAll": true
    }
  ]
}

Debug Views

Variables View

Shows all variables in current scope:

Local variables
Function parameters
this/self context
Global variables

Right-click variable:

  • Copy Value
  • Copy as Expression
  • Add to Watch
  • Set Value (modify during debug!)

Watch View

Add expressions to monitor:

1. Click "+" in Watch view
2. Enter expression: user.email
3. Updates as you step through code

Useful watch expressions:

// Object properties
user.profile.settings

// Array operations
items.length
items.filter(i => i.active).length

// Comparisons
count > threshold

Call Stack

Shows execution path:

processOrder (order.js:45)
  ↑ called by
validateOrder (order.js:23)
  ↑ called by
createOrder (api.js:12)
  ↑ called by
handleRequest (server.js:78)

Click any frame to:

  • View variables at that level
  • See code context
  • Navigate execution history

Debug Console

Execute code during debugging:

// Evaluate expressions
> user.name
"John Doe"

// Call functions
> calculateTotal(items)
150.25

// Modify variables
> discount = 0.2
0.2

// Complex expressions
> items.filter(i => i.price > 50)
[{...}, {...}]

Advanced Debugging Techniques

Debugging with Source Maps

TypeScript/Webpack Setup:

// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSourceMap": false,
    "inlineSources": false
  }
}
// launch.json
{
  "type": "node",
  "sourceMaps": true,
  "outFiles": ["${workspaceFolder}/dist/**/*.js"],
  "resolveSourceMapLocations": [
    "${workspaceFolder}/**",
    "!**/node_modules/**"
  ]
}

Remote Debugging

Debug on Remote Server:

# On remote server
node --inspect=0.0.0.0:9229 server.js
// Local launch.json
{
  "type": "node",
  "request": "attach",
  "name": "Attach to Remote",
  "address": "192.168.1.100",
  "port": 9229,
  "localRoot": "${workspaceFolder}",
  "remoteRoot": "/app"
}

Debug in Docker Container:

# Dockerfile
EXPOSE 9229
CMD ["node", "--inspect=0.0.0.0:9229", "index.js"]
// launch.json
{
  "type": "node",
  "request": "attach",
  "name": "Docker: Attach to Node",
  "port": 9229,
  "address": "localhost",
  "localRoot": "${workspaceFolder}",
  "remoteRoot": "/usr/src/app",
  "protocol": "inspector"
}

Debugging Tests

Jest with Breakpoints:

{
  "type": "node",
  "request": "launch",
  "name": "Jest Debug",
  "program": "${workspaceFolder}/node_modules/.bin/jest",
  "args": ["--runInBand", "--no-cache"],
  "console": "integratedTerminal"
}

Pytest with Breakpoints:

{
  "name": "Pytest Debug",
  "type": "python",
  "request": "launch",
  "module": "pytest",
  "args": ["-s", "${file}"],
  "console": "integratedTerminal"
}

Multi-Target Debugging

Debug Multiple Processes:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Worker 1",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/worker.js",
      "env": { "WORKER_ID": "1" }
    },
    {
      "name": "Worker 2",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/worker.js",
      "env": { "WORKER_ID": "2" }
    }
  ],
  "compounds": [
    {
      "name": "All Workers",
      "configurations": ["Worker 1", "Worker 2"]
    }
  ]
}

Pro Tips

Tip 1: Skip Files

Don't step into libraries:

{
  "skipFiles": [
    "<node_internals>/**",
    "**/node_modules/**",
    "**/dist/**/*.js"
  ]
}

Tip 2: Debug Current File

Quick debug without config:

1. Open file
2. F5
3. Select "Node.js" (or language)
4. Debugs current file immediately

Tip 3: Restart Frame

Right-click call stack frame → "Restart Frame"
Rewinds execution to start of function
Useful for re-testing with different values

Tip 4: Exception Breakpoints

Debug view → Breakpoints section
Check "Uncaught Exceptions"
Check "Caught Exceptions" (breaks on all errors)

Tip 5: Inline Values

{
  "debug.inlineValues": true
}

Shows variable values inline as you step through code!

Common Pitfalls

Pitfall 1: Source Maps Not Working

Problem: Breakpoints don't hit Solution: Verify source map configuration

{
  "sourceMaps": true,
  "outFiles": ["${workspaceFolder}/dist/**/*.js"],
  // Add this if still not working:
  "resolveSourceMapLocations": [
    "${workspaceFolder}/**",
    "!**/node_modules/**"
  ]
}

Pitfall 2: Can't Attach to Process

Problem: "Cannot connect to runtime process" Solution: Ensure process started with debug flag

# Node.js
node --inspect=9229 app.js

# Python
python -m debugpy --listen 5678 --wait-for-client app.py

Pitfall 3: Breakpoints Unverified (Gray)

Problem: Breakpoint is gray, not red Solutions:

  1. Check source maps enabled
  2. Verify file paths match
  3. Ensure code is transpiled
  4. Try "Restart Frame"

Pitfall 4: Variables Show ""

Problem: Can't see variable values Solution: Disable optimizations

// For Node.js
{
  "runtimeArgs": ["--no-lazy"]
}

// For TypeScript
// tsconfig.json
{
  "compilerOptions": {
    "optimization": false
  }
}

Pitfall 5: Too Many Breakpoints

Problem: Debug session overwhelming Solution: Use conditional breakpoints and logpoints

Instead of: 10 breakpoints in loop
Use: 1 conditional breakpoint: i === targetValue
Or: Logpoint to track progress

Assessment

Practical Exercise

Debug a Bug:

// Bug: This function doesn't correctly calculate average
function calculateAverage(numbers) {
  let sum = 0;
  for (let i = 0; i <= numbers.length; i++) {
    sum += numbers[i];
  }
  return sum / numbers.length;
}

console.log(calculateAverage([1, 2, 3, 4, 5])); // Should be 3, but returns NaN

Debugging steps:

  1. Set breakpoint on line 2
  2. Start debugging
  3. Watch sum, i, numbers[i]
  4. Step through loop
  5. Notice i <= numbers.length (should be <)
  6. Notice accessing numbers[5] = undefined
  7. Fix bug
  8. Retest

Success Criteria:

  • Identified bug using debugger
  • Used watch expressions
  • Stepped through code
  • Fixed and verified

Next Steps

  • Task Automation: Automate debugging with tasks
  • Advanced Editing: Advanced debugging patterns

Resources


Time: 3-4 hours Difficulty: Intermediate Prerequisites: Programming experience