DEV Community

Alex Chen
Alex Chen

Posted on

Debugging JavaScript: From console.log to Pro (2026)

Debugging JavaScript: From console.log to Pro (2026)

Every developer spends more time debugging than writing code. The difference between juniors and seniors isn't writing bug-free code — it's finding bugs faster.

Beyond console.log

// You already know these:
console.log('Basic log');
console.error('Error message');
console.warn('Warning message');

// But these are MORE useful:

// Formatted logging (%s=string, %o=object, %d=number, %c=CSS style):
console.log('%c ERROR %c User %s not found', 
  'color: red; font-weight: bold; background: #ffe0e0; padding: 2px 6px;',
  'color: blue;', userId);
// Output: Red "ERROR" badge + blue userId

// Table view for arrays of objects:
const users = [
  { id: 1, name: 'Alice', role: 'admin', active: true },
  { id: 2, name: 'Bob', role: 'user', active: false },
];
console.table(users, ['name', 'role']); // Only show selected columns!

// Grouping related logs:
console.group('User Authentication');
  console.log('Step 1: Validate token');
  console.log('Step 2: Fetch user');
  console.log('Step 3: Check permissions');
console.groupEnd();
// Collapsible in browser DevTools!

// Timing operations:
console.time('API call');
await fetch('/api/data');
console.timeEnd('API call'); // Output: "API call: 243.45ms"

// Assert (log only if condition is false):
function processUser(user) {
  console.assert(user.email, 'User must have email:', user); // Shows stack trace!
  // ... rest of function
}

// Trace (shows how you got here):
function helper() {
  console.trace('Called from:');
}
function main() {
  helper(); // Shows full call stack leading to this point
}

// Count (track how many times something runs):
let renderCount = 0;
function render() {
  renderCount++;
  console.count('render called'); // "render called: 1", "render called: 2", ...
}
Enter fullscreen mode Exit fullscreen mode

Chrome DevTools Debugger

// Instead of adding/removing console.logs, use the debugger statement:
function complexCalculation(a, b) {
  const sum = a + b;
  debugger; // Pauses execution here! Inspect variables in DevTools.
  return sum * 2;
}

// Conditional breakpoint (in DevTools Sources panel):
// Right-click line number → "Add conditional breakpoint"
// Enter: count > 100  → Only pauses when condition is true
// Enter: user.id === 'abc123' → Only pauses for specific user

// Logpoint (non-breaking console.log at breakpoint):
// Right-click → "Add logpoint"
// Enter: 'User ID:', userId  → Logs without pausing!
// Great for production debugging where you can't stop execution

// XHR/Fetch breakpoints (DevTools → Sources → XHR breakpoints):
// Set to break on any URL containing "/api/"
// Or break on specific request types

// DOM breakpoints:
// Select element in Elements tab → right-click → Break on:
// - Subtree modifications (when children change)
// - Attribute modifications (when attributes change)
// - Node removal (when element is deleted)
// Invaluable for tracking down rogue JS modifying your DOM!

// Console API tricks available in DevTools:
$0              // Currently selected element in Elements tab
$1              // Previously selected element
$('button')     // querySelector alias
$$('.item')     // querySelectorAll alias
copy(data)      // Copy to clipboard
keys(object)    // Show object keys
values(object)  // Show object values
monitor(fn)     // Log every time fn is called with arguments
monitorEvents(element, 'click')  // Log all click events on element
Enter fullscreen mode Exit fullscreen mode

Node.js Debugging

// Built-in inspector (Node.js 6.3+):
// node --inspect app.js
// Then open chrome://inspect in Chrome and connect!

// Or inspect on first line:
// node --inspect-brk app.js

// Debugging with VS Code:
// Create .vscode/launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Server",
      "program": "${workspaceFolder}/src/server.js",
      "skipFiles": ["<node_internals>/**"],
      "env": { "NODE_ENV": "development" }
    },
    {
      "type": "node",
      "request": "attach",
      "name": "Attach to Running",
      "port": 9229,
      "restart": true,
      "stopOnEntry": false
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Run Current File",
      "program": "${file}",
      "console": "integratedTerminal"
    }
  ]
}

// Useful launch configurations:

// Debug Mocha tests:
{
  "type": "node",
  "request": "launch",
  "name": "Mocha All",
  "program": "${workspaceFolder}/node_modules/.bin/mocha",
  "args": ["--timeout", "10000", "${workspaceFolder}/test/**/*.js"]
}

// Debug with environment-specific .env:
// Install: npm install dotenv-cli
{
  "type": "node",
  "request": "launch",
  "envFile": "${workspaceFolder}/.env.development",
  "program": "${workspaceFolder}/src/server.js"
}
Enter fullscreen mode Exit fullscreen mode

Source Maps & Production Debugging

// Source maps connect your compiled/minified code back to original source:
// tsconfig.json:
{
  "compilerOptions": {
    "sourceMap": true,
    "sourceRoot": "./src",
    "inlineSourceMap": false,   // Separate .map file (don't inline in prod!)
    "mapRoot": "./dist/maps"
  }
}

// webpack/vite config:
module.exports = {
  devtool: 'source-map',        // Best for production (separate file)
  // devtool: 'cheap-module-source-map',  // Faster rebuilds for dev
};

// NEVER upload source maps to public servers in production!
// They expose your entire source code.
// Options:
// 1. Don't generate source maps in production builds
// 2. Generate but don't deploy them (keep internally for error reporting)
// 3. Use a service that requires authentication to access maps (Sentry, etc.)

// Error tracking with source map support:
// Sentry automatically applies source maps when you upload them:
// npx @sentry/cli sourcemaps upload ./dist
// Now errors show the ORIGINAL code line, not minified code!
Enter fullscreen mode Exit fullscreen mode

Common Bugs & How to Find Them Fast

// Bug #1: "undefined is not a function" / "Cannot read property of undefined"
// Cause chain: variable is undefined → accessing property → crash
// Quick fix: Add optional chaining to find where it breaks:
// Before: data.user.address.city
// After:  data?.user?.address?.city
// If this doesn't crash → the problem is somewhere in that chain

// Bug #2: Async/await error swallowed silently
async function broken() {
  const data = await fetch('/api'); // If this throws...
  processData(data);                 // ...this never runs, but no error shown!
}
// Fix: Always use try/catch or .catch():
async function fixed() {
  try {
    const data = await fetch('/api');
    processData(data);
  } catch (err) {
    console.error('Fetch failed:', err); // NOW you see the error!
  }
}
// Or add global handler:
process.on('unhandledRejection', (reason) => {
  console.error('Unhandled rejection:', reason);
});

// Bug #3: Race conditions
let cachedData;
async function getData() {
  if (!cachedData) {
    cachedData = await slowFetch(); // Two calls arrive simultaneously!
  }                                   // Both see null → both fetch!
  return cachedData;
}
// Fix: Use promise caching (not value caching):
let fetchPromise;
async function getDataFixed() {
  if (!fetchPromise) {
    fetchPromise = slowFetch().finally(() => { fetchPromise = null; });
  }
  return fetchPromise;
}
// Now concurrent calls share the SAME promise!

// Bug #4: Memory leak detection
// In DevTools → Memory tab:
// 1. Take heap snapshot
// 2. Do the operation that might leak
// 3. Take another heap snapshot
// 4. Compare snapshots → objects growing = leak suspects
// Look for "(detached)" elements — DOM nodes referenced by JS but removed from page

// Bug #5: Off-by-one errors in loops
for (let i = 0; i <= array.length; i++) { // ≤ instead of < !
  // Crashes on last iteration (array[length] = undefined)
}
// Fix: Use for...of when you don't need index:
for (const item of array) { /* safe */ }
// Or use .forEach(), .map(), .filter() — no index bugs possible
Enter fullscreen mode Exit fullscreen mode

What's your favorite debugging technique? What's the weirdest bug you've ever found?

Follow @armorbreak for more practical developer guides.

Top comments (0)