Quick Answer

Set a breakpoint by clicking the gutter left of a line number, then press F5 to start debugging. Step through with F10 (over) and F11 (into), inspect state in the VARIABLES and WATCH panes, and evaluate expressions live in the Debug Console. For loops and rare inputs, use conditional breakpoints; to log without stopping or editing code, use logpoints. Save the setup in .vscode/launch.json so it is one keypress next time.

Breakpoints beat print statements

Open the Run and Debug view from the sidebar and start a session with F5. Set a breakpoint by clicking in the margin to the left of a line number; a red dot appears and execution pauses there when it is reached.

Once paused, the debug toolbar gives you Continue (F5) to run to the next breakpoint, Step Over (F10) to run the next line without descending into function calls, Step Into (F11) to follow a call, and Step Out (Shift+F11) to finish the current function and return, plus Restart and Stop.

While paused, the VARIABLES pane shows locals and closure state for the frame selected in the CALL STACK pane, so click up the stack to see the caller's state. Hover any variable in the editor to inspect it, and VS Code also shows current values inline next to the code. This loop is faster than adding a print, re-running, reading the output, and deleting the print.

Conditional breakpoints and hit counts

A plain breakpoint inside a loop that runs 5,000 times is useless. Right-click the margin and choose Add Conditional Breakpoint, or right-click an existing breakpoint and pick Edit Breakpoint. You get three modes:

  • Expression: pauses only when the expression is true, for example order.id === 4173 or items.length === 0. It runs in the current scope on every pass, so keep it cheap.
  • Hit count: pauses after the line has been reached a set number of times, for example >500 or =1. Useful when the bug appears only after a while.
  • Triggered breakpoint: stays inactive until another named breakpoint is hit first, so you only pause inside a shared helper when it is called from the path you care about.

Conditions turn step-through-everything-and-hope into stop-exactly-at-the-bad-record. It is the single highest-value debugging feature most people never turn on.

Logpoints: tracing without editing code

A logpoint is a breakpoint that logs a message instead of pausing. Right-click the margin, choose Add Logpoint, and type a message. Expressions in curly braces are interpolated, for example user {user.id} role {user.role} at step {i}.

When execution reaches that line, VS Code prints the message to the Debug Console and keeps running. Nothing pauses, and you never touch the source file, so there is no stray console.log to forget and commit, and no rebuild in languages that need one.

This is the right tool when you want a timeline of what happened across many iterations or requests, rather than a snapshot at one moment. Set a few logpoints along a code path, run the scenario once, and read the sequence in the console. You can enable, disable and remove them from the BREAKPOINTS pane without editing anything, and a logpoint can carry a condition too.

The Debug Console is a REPL

While the program is paused, the Debug Console is a full read-eval-print loop running in the context of the selected stack frame.

You can do more than print a variable. Call a function with test arguments, reassign a value and continue to see what changes, evaluate the exact expression from a failing assertion, or check what a library helper returns for your input. Autocomplete works as you type, and Shift+Enter lets you write multi-line statements.

For values you want to track continuously, add them to the WATCH pane; each expression there is re-evaluated every time execution pauses, so you see it change as you step. Between conditional breakpoints to reach the right moment, the Debug Console to probe and test a fix, and the WATCH pane to follow state, you rarely need to restart a session just to check one more thing.

launch.json for real projects

For anything beyond debugging the current file, create .vscode/launch.json: in the Run and Debug view, click create a launch.json file and pick your environment. Every configuration needs three keys: type (the debugger, for example node), request (launch to start your app, or attach to connect to a running one), and name (what shows in the dropdown).

Useful optional keys: program, args, cwd, env and envFile, console set to integratedTerminal when your program reads standard input, preLaunchTask to build first, and skipFiles to stop stepping into dependencies and runtime internals. Variables like ${workspaceFolder} and ${file} keep paths portable.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Run API",
      "program": "${workspaceFolder}/src/server.js",
      "args": ["--port", "4000"],
      "envFile": "${workspaceFolder}/.env",
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"]
    }
  ]
}

A compounds array can start several configurations together, for example a backend and a frontend in one keypress.

Frequently Asked Questions

How do I make a breakpoint trigger only for a specific value? Right-click the breakpoint or the gutter and choose Add Conditional Breakpoint, then enter an expression such as id === 42. Execution pauses only when it evaluates to true. The expression runs in the current scope every time the line is reached.
What is a logpoint? A breakpoint that logs a message to the Debug Console instead of pausing. Add it by right-clicking the gutter and choosing Add Logpoint. Text in curly braces is evaluated, so you can trace values across many iterations without adding print statements to your code.
How do I debug a program that needs command-line arguments? Add an args array to the configuration in launch.json, with each argument as a separate string. For programs that read from standard input, also set the console option to integratedTerminal so you can type into them.
Can I run code while the debugger is paused? Yes. The Debug Console is a REPL that runs in the selected stack frame's scope. You can call functions, change variables and evaluate any expression, which is the fastest way to test a fix before editing the file.
How do I attach the debugger to an already-running process? Use a configuration with request set to attach instead of launch. Start your app with the debugger enabled (for Node, the --inspect flag), then set the matching port or pick the process in launch.json and run the attach configuration.