Toolmingo
Guides7 min read

How to Debug JavaScript: A Complete Guide

Master JavaScript debugging with Chrome DevTools, the debugger statement, console methods, async debugging, Node.js debugging, and source maps. Practical techniques for squashing bugs fast.

Debugging is not about luck — it's a skill. This guide gives you a systematic toolkit: from console.log to Chrome DevTools breakpoints to async error tracing in Node.js. Work through these techniques once and you'll squash bugs in minutes instead of hours.


Quick-reference table

Technique When to use
console.log / console.table Fast sanity checks on values
debugger statement Pause execution at an exact line
DevTools Breakpoints Step through code without touching source
DevTools Network tab Debug fetch/XHR failures
DevTools Console Run expressions in the current scope
try/catch + console.error Catch and surface runtime errors
Source maps Debug minified/transpiled code in production
node --inspect Debug Node.js in Chrome DevTools
VS Code debugger Full IDE debugging with watch variables
performance.now() Pinpoint slow code sections

1. Console methods beyond console.log

Most developers use only console.log. The full API is much richer.

// Basic output levels
console.log('Info:', value);
console.warn('Warning:', value);   // yellow in DevTools
console.error('Error:', value);    // red + stack trace

// Tabular output — perfect for arrays of objects
const users = [{ id: 1, name: 'Ana' }, { id: 2, name: 'Ivo' }];
console.table(users);

// Grouping related output
console.group('User validation');
  console.log('name:', name);
  console.log('email:', email);
console.groupEnd();

// Timing a block of code
console.time('fetchUsers');
await fetchUsers();
console.timeEnd('fetchUsers');  // → fetchUsers: 143.2ms

// Assert — only logs if condition is false
console.assert(arr.length > 0, 'Array must not be empty', arr);

// Stack trace at any point
console.trace('Called from here');

// Count how many times a point is hit
function render() {
  console.count('render');  // → render: 1, render: 2 …
}

Tip: console.log({ value }) prints { value: 42 } — the variable name is shown automatically because you're using shorthand object notation.


2. The debugger statement

Insert debugger anywhere in your code. When DevTools is open, execution pauses at that line — just like a breakpoint, but in source.

function calculateTotal(items) {
  debugger;   // DevTools pauses here
  return items.reduce((sum, item) => sum + item.price, 0);
}

Remove it before committing — linters (ESLint no-debugger rule) catch stray debugger statements in CI.


3. Chrome DevTools: breakpoints and step execution

Open DevTools with F12 or Ctrl+Shift+I (Windows/Linux) / Cmd+Option+I (macOS).

Setting a breakpoint

  1. Go to the Sources tab.
  2. Open your file in the file tree.
  3. Click a line number — a blue marker appears.
  4. Reload or trigger the code path.

Stepping controls

Button Shortcut Action
▶ Resume F8 Continue to next breakpoint
↷ Step over F10 Run the current line, stay in this function
↓ Step into F11 Enter the function called on this line
↑ Step out Shift+F11 Finish current function, return to caller

The Scope panel

While paused, the Scope panel on the right shows all variables in the current closure — local, closure, and global scopes. Hover over a variable in the source to see its live value.

Conditional breakpoints

Right-click a line number → Add conditional breakpoint. Enter an expression like user.id === 42. The breakpoint only fires when the condition is true — ideal for loops with thousands of iterations.

Logpoints (log without pausing)

Right-click a line number → Add logpoint. Enter a message like "price: " + item.price. The expression is logged to the Console every time execution hits that line — no console.log clutter in source.


4. Debugging network requests

In the Network tab:

  • Filter by Fetch/XHR to isolate API calls.
  • Click a request to see Headers, Payload (request body), Preview and Response.
  • Check the Timing subtab for slow requests (TTFB, download time).
  • Throttle the connection with the dropdown (Fast 3G, Slow 3G) to test offline / slow-network behaviour.

5. JavaScript error types

Understanding the error type tells you where to look.

Error Cause Example
SyntaxError Invalid JS syntax (parse time) if (x {
ReferenceError Variable not declared console.log(foo) before let foo
TypeError Wrong type or undefined method null.toString()
RangeError Value out of allowed range new Array(-1)
URIError Malformed URI in decodeURIComponent decodeURIComponent('%')
EvalError eval() misuse (rare in modern code)

Read the stack trace bottom-up — the bottom frame is where the error originated; the top is where it surfaced.


6. Debugging async code

Async bugs are the trickiest because errors surface far from their cause.

Unhandled promise rejection

// WRONG — swallows the rejection
fetchData().then(process);

// CORRECT — always chain .catch()
fetchData()
  .then(process)
  .catch(err => console.error('fetchData failed:', err));

// With async/await
async function load() {
  try {
    const data = await fetchData();
    process(data);
  } catch (err) {
    console.error('load failed:', err, err.stack);
  }
}

Enable "Pause on caught exceptions" and "Pause on uncaught exceptions" in DevTools Sources → the ⏸ icon. This pauses execution at the exact throw site.

Async stack traces

Modern Chrome DevTools (v73+) captures async call stacks automatically. You'll see async frames in the call stack panel labelled "Async call from …".

For Node.js, set Error.stackTraceLimit = 50 at startup to capture deeper stacks.


7. Source maps

If you deploy minified/transpiled code (TypeScript, bundled React, etc.) you'll see unhelpful stack traces like bundle.js:1:4523. Source maps fix this.

In Vite / webpack, source maps are enabled automatically in development mode. For production debugging, generate a separate .map file and configure your error tracker (Sentry, Datadog) to upload it privately — never serve the .map file publicly.

// vite.config.ts — enable source maps in build
export default {
  build: {
    sourcemap: true,   // or 'hidden' to not serve publicly
  }
}

In Chrome DevTools, go to Settings → Preferences → Sources and enable "Enable JavaScript source maps" (default: on).


8. Node.js debugging

Option A: node --inspect

node --inspect server.js
# Node listens on ws://127.0.0.1:9229

# Or break at start
node --inspect-brk server.js

Open chrome://inspect in Chrome → click inspect under "Remote Target". You get the full DevTools UI connected to your Node process.

Option B: VS Code debugger

Create .vscode/launch.json:

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

Press F5 to start. Set breakpoints by clicking the gutter. The Watch panel lets you evaluate expressions live. The Debug Console at the bottom accepts any JS expression in the current scope.


9. Performance debugging

When your app is slow:

// Quick timing
const start = performance.now();
heavyCalculation();
console.log(`Took ${(performance.now() - start).toFixed(2)}ms`);

For deeper analysis: DevTools Performance tab → Record → interact with the page → Stop. Look for long tasks (red bars), layout thrashing (interleaved reads/writes), and excessive re-renders.

The Memory tab's Heap Snapshot catches memory leaks: take a snapshot, do an action, take another — compare "Alloc. between snapshots" to see what's accumulating.


6 common debugging mistakes

Mistake Fix
Using == instead of === for comparisons Always use ===; type coercion hides bugs
Mutating state directly in React Use spread/structuredClone; state should be immutable
console.log objects by reference Use console.log(JSON.parse(JSON.stringify(obj))) to snapshot the value at that moment
Ignoring the call stack Read the full stack trace, not just the top line
Commenting out code to isolate bugs Use binary search: disable half the code at a time
Leaving debugger in production Add ESLint no-debugger rule to prevent it

FAQ

Q: Why does console.log show an object but its properties are empty?
The DevTools console evaluates objects lazily. By the time you expand the object, its properties may have changed. Use console.log(JSON.stringify(obj)) or structuredClone(obj) to freeze the snapshot.

Q: Breakpoints disappear after a page reload. Why?
If DevTools opens after the breakpoint file loads, the source map may not match. Try: check Settings → Blackboxing hasn't excluded your file, and ensure the file hasn't been chunked differently by your bundler.

Q: How do I debug a TypeError: Cannot read properties of undefined?
Walk up the call stack. The object that's undefined was likely not returned or was overwritten earlier. Add a breakpoint before the line and inspect the variable in the Scope panel.

Q: How do I debug a production error with no source maps?
Use an error tracker (Sentry, Datadog, Bugsnag). Configure it to upload source maps at deploy time so traces are de-minified in the dashboard — without exposing .map files publicly.

Q: What's the difference between Step Over and Step Into?
Step Over (F10) runs the current line and stops at the next line in the same function — it skips into any function calls on that line. Step Into (F11) enters the first function called on the current line.

Q: How do I find which code is causing a UI freeze?
Open the DevTools Performance tab, click Record, reproduce the freeze, stop recording. Look for long Task blocks (>50ms is "long task"). The flame chart shows which function took the most CPU time.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools