Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 9 – Errors and debugging

Debugging in DevTools and Node

Reproduce and shrink a bug, use breakpoints, conditional breakpoints and logpoints in DevTools and Node.js, and read minified stack traces with source maps.

  • Intermediate
  • 22 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 22 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Apply a method to a bug: reproduce it, shrink it, form a hypothesis and test it
  • Use breakpoints, conditional breakpoints, logpoints, watch expressions and the call stack
  • Debug Node.js programs with --inspect and Chrome DevTools, or with node inspect in a terminal
  • Map minified or compiled code back to its sources with source maps

Before you start

What you’ll learn · Before you start

What you will learn

  • Apply a method to a bug: reproduce it, shrink it, form a hypothesis and test it
  • Use breakpoints, conditional breakpoints, logpoints, watch expressions and the call stack
  • Debug Node.js programs with --inspect and Chrome DevTools, or with node inspect in a terminal
  • Map minified or compiled code back to its sources with source maps

Before you start

On this page

Debugging is finding out why a program does what it does instead of what you meant. A debugger makes that a matter of looking rather than guessing: it pauses the running program at a line you choose, a breakpoint, shows every variable in scope and the chain of calls that led there, and steps through the code one line at a time. Browsers have one in their developer tools, the Sources panel in Chrome; Node.js offers the same protocol with --inspect, so Chrome DevTools can attach to it, and has a terminal debugger, node inspect. When the code that runs is minified or compiled, source maps lead from its positions back to the source you wrote. Tools help only inside a method, though: make the bug happen on demand, shrink it, form one hypothesis and test it. This lesson follows that method on a cart whose discount is wrong, with a debugging session recorded as it ran.

A method before any tool

Five steps of debugging: reproduce, shrink, form a hypothesis, test it until one is confirmed, then fix the cause and keep a test.1. Reproducea failing input you can run again,ideally as a test2. Shrinkthe smallest input and the least codethat still fail3. Form a hypothesisone specific claim about the cause4. Test ita breakpoint, a watch or a logpointthat could prove it wrong5. Fix the causeand keep the failing input as a testproved wrongconfirmed

A method for finding bugs

Text description of the diagram

The chart shows five steps from top to bottom, with one loop.

1. **Reproduce:** find a failing input you can run again, ideally as a test. 2. **Shrink:** cut it down to the smallest input and the least code that still fail. 3. **Form a hypothesis:** make one specific claim about the cause. 4. **Test it:** use a breakpoint, a watch expression or a logpoint that could prove the claim wrong. If it is proved wrong, go back to step 3 with what you learned and form another hypothesis. 5. **Fix the cause** once a hypothesis is confirmed, and keep the failing input as a test so that the bug cannot come back unnoticed.

Reproduce comes first because a bug you cannot trigger cannot be checked as fixed. Write the failing input down, ideally as a test, and note the exact versions it fails on. Shrink it next: remove input and code until nothing more can go without the failure going too. When the bug is new, git bisect finds the commit that introduced it by a binary search through the history, with git bisect run testing each candidate automatically. Then form one hypothesis, a claim specific enough to be wrong (“the coupon’s cap is applied as a minimum”), and test it with an observation that could disprove it. If it is disproved, the new fact goes into the next hypothesis. When one survives, fix the cause and keep the failing input as a regression test.

The bug: a discount that is too big

A cart holds two packs of tea at 120.00 and a notebook at 45.00, 285.00 in all. A coupon gives 10 % off, at most 50.00, so the discount should be 28.50 and the total 256.50, but checkout says 235.00. Reading shop.mjs might reveal the mistake, and in a large program it often would not; a debugger shows it without guessing.

The session below was recorded by debug-session.mjs. It starts a small debugger in a worker thread, which connects to the program’s own inspector, as the Node.js documentation recommends for setting breakpoints from code, and speaks the same protocol as Chrome DevTools. It sets three breakpoints before shop.mjs loads, runs the checkout, and prints what it saw at each pause.

Three breakpoints on a cart total, recorded

JavaScript · debug-session.mjs

// A recorded debugging session of shop.mjs: a logpoint, a conditional breakpoint and a breakpoint with watches,
// set through Node.js's inspector before the module loads.
import { once } from 'node:events';
import { Worker } from 'node:worker_threads';

const breakpoints = [
  // A logpoint: a condition that logs and is never true, so the program does not stop.
  { line: 5, condition: "console.log(`logpoint: ${item.sku} x ${item.qty} = ${item.qty * item.unitPaise}`), false", watch: [] },
  // A conditional breakpoint: stops only for lines with more than one unit.
  { line: 6, condition: 'item.qty > 1', watch: ['item', 'line', 'subtotal'] },
  // A plain breakpoint before the return, with the call stack and three watch expressions.
  { line: 10, stack: true, watch: ['subtotal', 'percentOff', 'discount'] },
];
const debuggerThread = new Worker(new URL('./debugger-worker.mjs', import.meta.url), { workerData: { file: 'shop.mjs', breakpoints } });
await once(debuggerThread, 'message'); // the breakpoints are set

const { checkout } = await import('./shop.mjs');
const items = [{ sku: 'TEA-250', qty: 2, unitPaise: 12000 }, { sku: 'NOTE-A5', qty: 1, unitPaise: 4500 }];
console.log(`checkout says: ${checkout(items, { percent: 10, capPaise: 5000 })} (expected: total 256.50)`);

debuggerThread.postMessage('done');
const [{ pauses }] = await once(debuggerThread, 'message');
await debuggerThread.terminate();
console.log('\nWhat the debugger saw at each pause:');
pauses.forEach((lines, i) => console.log(`${i + 1}. ${lines.join('\n   ')}`));

Output

logpoint: TEA-250 x 2 = 24000
logpoint: NOTE-A5 x 1 = 4500
checkout says: total 235.00 (expected: total 256.50)

What the debugger saw at each pause:
1. shop.mjs:6 in cartTotal, because item.qty > 1
   item = {"sku":"TEA-250","qty":2,"unitPaise":12000}
   line = 24000
   subtotal = 0
2. shop.mjs:10 in cartTotal
   call stack: cartTotal <- checkout <- (module)
   subtotal = 28500
   percentOff = 2850
   discount = 5000

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node debug-session.mjs

shop.mjs

// The code under the debugger: a cart total with a coupon whose discount should be capped.
export function cartTotal(items, coupon) {
  let subtotal = 0;
  for (const item of items) {
    const line = item.qty * item.unitPaise;
    subtotal += line;
  }
  const percentOff = Math.round((subtotal * coupon.percent) / 100);
  const discount = Math.max(percentOff, coupon.capPaise);
  return subtotal - discount;
}

export function checkout(items, coupon) {
  const total = cartTotal(items, coupon);
  return `total ${(total / 100).toFixed(2)}`;
}

debugger-worker.mjs

// A small debugger: it runs in a worker thread, connects to the main thread's inspector, as the Node.js documentation
// recommends for breakpoints, and talks the Chrome DevTools Protocol, the same protocol DevTools uses.
import { Session } from 'node:inspector';
import { parentPort, workerData } from 'node:worker_threads';

const session = new Session();
const pauses = [];
// When the program is done, hand over what was seen; the main thread then stops this thread.
parentPort.on('message', (message) => {
  if (message === 'done') parentPort.postMessage({ pauses });
});
session.connectToMainThread();
const post = (method, params) => new Promise((resolve, reject) => session.post(method, params, (error, result) => (error ? reject(error) : resolve(result))));

const watches = new Map(); // breakpoint id -> what to show when it pauses
session.on('Debugger.paused', async ({ params }) => {
  const [frame] = params.callFrames;
  const spec = watches.get(params.hitBreakpoints[0]);
  const lines = [`${workerData.file}:${frame.location.lineNumber + 1} in ${frame.functionName}${spec.condition ? `, because ${spec.condition}` : ''}`];
  if (spec.stack) lines.push(`call stack: ${params.callFrames.map((f) => f.functionName || '(module)').join(' <- ')}`);
  for (const expression of spec.watch) {
    const { result } = await post('Debugger.evaluateOnCallFrame', { callFrameId: frame.callFrameId, expression: `JSON.stringify(${expression})` });
    lines.push(`${expression} = ${result.value}`);
  }
  pauses.push(lines);
  await post('Debugger.resume');
});

await post('Debugger.enable');
for (const spec of workerData.breakpoints) {
  const urlRegex = `${workerData.file.replace(/\./g, '\\.')}$`;
  const { breakpointId } = await post('Debugger.setBreakpointByUrl', { lineNumber: spec.line - 1, urlRegex, condition: spec.condition ?? '' });
  watches.set(breakpointId, spec);
}
parentPort.postMessage('ready');
  • A logpoint on line 5 printed each line’s amount without stopping the program. It is a breakpoint whose condition calls console.log and evaluates to false, so the debugger never pauses there; DevTools offers the same as “Add logpoint”.
  • A conditional breakpoint on line 6 paused only where item.qty > 1 was true, once for the tea, instead of on every pass of the loop. In a loop over 10,000 items, that difference is what makes a breakpoint usable.
  • A breakpoint on line 10 paused before the return, with the call stack, cartTotal, called from checkout, called from the module, and three watch expressions.

The last pause holds the answer: percentOff is 2850, the 10 %, but discount is 5000, the cap. The code took the larger of the two values instead of the smaller, so line 9 needs Math.min instead of Math.max: the hypothesis “the cap is applied as a minimum” survived. Note that the pauses are listed after the program’s own output only because the session collects them while the program is stopped and prints them at the end.

Breakpoints in the browser’s developer tools

In Chrome, open the developer tools, go to the Sources panel and open the file. A click on a line number sets a breakpoint; a right-click offers a conditional breakpoint and a logpoint. While the code is paused, the Scope pane lists the variables, the Watch pane evaluates expressions you add, and the Call Stack pane shows how the code got there.

Kind How it is set Use it when
Line breakpoint Click the line number You know where to look
Conditional breakpoint Right-click the line number, add a condition Only some passes through a line matter
Logpoint Right-click the line number, add a message You want console.log output without editing the code
Exception breakpoint “Pause on uncaught exceptions”, “Pause on caught exceptions” You want to stop where an error is thrown, not where it is reported
DOM, fetch and event listener breakpoints The Elements panel, and the panes of the Sources panel You do not know which code changes a node, sends a request or handles a click
Action while paused Key in Chrome
Resume F8
Step over the next call F10
Step into the next call F11
Step out of the current function Shift+F11

The debugger; statement sets a breakpoint from inside the code. ECMA-262 lets it pause only when a debugger is present and active, so it does nothing for visitors without the developer tools open, but it should still never ship: ESLint’s recommended no-debugger rule reports it.

Debugging Node.js programs

node --inspect-brk app.mjs starts a program with its inspector open and stops before the first line runs; plain --inspect does not wait. Open chrome://inspect in Chrome, where the program appears under Remote Target, and DevTools attaches with the same Sources panel as for a page. Code editors such as Visual Studio Code attach to the same port. Without a browser, node inspect app.mjs starts a debugger in the terminal:

Command What it does
sb(12), sb('shop.mjs', 6, 'item.qty > 1') Set a breakpoint on a line of the current file, or a conditional one in a file
c, n, s, o Continue, step to the next line, step into, step out
watch('discount'), watchers Add a watch expression, list the watched values
exec subtotal, repl Evaluate an expression, or open a prompt in the paused scope
bt Print the backtrace, the call stack

Node.js 24.16 added an experimental probe mode for printf-style debugging without editing code: node inspect --probe shop.mjs:10 --expr 'discount' app.mjs prints the value each time execution reaches that line.

The inspector is powerful, which makes it a door into the process. It listens on 127.0.0.1:9229 by default, reachable only from the same machine, and the Node.js documentation warns that binding it to a public address, including 0.0.0.0, allows remote code execution. To debug a remote server, keep the default and forward the port over SSH, as in ssh -L 9221:localhost:9229 user@server, then attach to localhost:9221. A running process can be told to open its inspector with the SIGUSR1 signal, which Node.js reserves for that purpose.

Source maps: from minified code back to your source

Bundlers and compilers turn your files into code that is smaller or targets older engines, and an error from that code points at positions nobody can read, such as line 1, column 49 of a minified file. A source map is a JSON file that maps positions in the generated code back to files, lines, columns and names of the original; its format is standardised by Ecma as ECMA-426. The generated file names its map in a comment such as //# sourceMappingURL=cart.min.mjs.map.

One stack trace, without and with source maps

JavaScript · sourcemap-stack.mjs

// The same error from the minified bundle, first as Node.js reports it by default, then with source maps applied.
import { setSourceMapsSupport } from 'node:module';

const items = [{ qty: 1, pricePaise: 12000 }, { qty: '2', pricePaise: 25000 }];
const topFrames = (error) => error.stack.split('\n').slice(0, 3).join('\n');

const plain = await import('./dist/cart.min.mjs');
try {
  plain.cartTotalPaise(items);
} catch (error) {
  console.log(`Without source maps:\n${topFrames(error)}`);
}

// The same as starting Node.js with --enable-source-maps, for modules loaded from now on.
setSourceMapsSupport(true);
const mapped = await import('./dist/cart.min.mjs?with-source-maps');
try {
  mapped.cartTotalPaise(items);
} catch (error) {
  console.log(`\nWith source maps:\n${topFrames(error)}`);
}

Output

Without source maps:
TypeError: qty must be a whole number, got "2"
    at n (dist/cart.min.mjs:1:49)
    at Module.o (dist/cart.min.mjs:1:189)

With source maps:
TypeError: qty must be a whole number, got "2"
    at linePaise (src/cart.mjs:5:11)
    at Module.o (src/cart.mjs:12:38)

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node sourcemap-stack.mjs

src/cart.mjs · src/cart.mjs

// What the team wrote. dist/cart.min.mjs is this file after the build (esbuild 0.28.2: --bundle --minify
// --sourcemap --format=esm), and dist/cart.min.mjs.map maps its positions back here.
function linePaise(item) {
  if (!Number.isInteger(item.qty)) {
    throw new TypeError(`qty must be a whole number, got ${JSON.stringify(item.qty)}`);
  }
  return item.qty * item.pricePaise;
}

export function cartTotalPaise(items) {
  let total = 0;
  for (const item of items) total += linePaise(item);
  return total;
}

dist/cart.min.mjs · dist/cart.min.mjs

function n(t){if(!Number.isInteger(t.qty))throw new TypeError(`qty must be a whole number, got ${JSON.stringify(t.qty)}`);return t.qty*t.pricePaise}function o(t){let r=0;for(let e of t)r+=n(e);return r}export{o as cartTotalPaise};
//# sourceMappingURL=cart.min.mjs.map

Without source maps, the trace names functions n and o at columns of the single line of the bundle. With them, the same error points at linePaise in src/cart.mjs, line 5, where the TypeError is thrown, and at line 12, the loop that called it. The second frame is still named o: positions map reliably, while names are restored only where the map records them. Developer tools in browsers load source maps by themselves when they are open. Node.js applies them to stack traces only when asked: start it with --enable-source-maps, or call module.setSourceMapsSupport(true) before loading the code, as the example does. The documentation warns that reading error.stack becomes slower with source maps on, which matters only for programs that do it constantly.

In production, keep the maps where your error reports can use them without serving them to every visitor if your source should stay private, for example by uploading them to your error tracker as part of each release.

Source Map Stack Trace Decoder (Unminify JavaScript Errors) Paste a minified stack trace and its .map file to see the original files, lines and functions. JavaScript Formatter Spread a one-line minified file over readable lines before you step through it.

console.log, used on purpose

Printing values is a legitimate debugging tool when it is deliberate: print the value you are checking, labelled, at the point where your hypothesis says it goes wrong, and remove the line once you know. A logpoint does the same without touching the file. One trap belongs to browsers: their console reads a logged object’s contents when you expand it, not when it was logged, so log a copy, or the fields you need, when the object keeps changing. The console and reading error messages covers the console’s other methods.

Exercise: find the line where the invoice goes wrong

Exercise · Medium · JavaScript

Find the line where the invoice goes wrong

A shop shows an invoice preview while the customer edits the cart. Customers report that the preview is right the first time and then drops every time the page refreshes it, although nothing in the cart changed. invoice(lines) is the code behind the preview; amounts are in paise.

  1. Reproduce it first: run the sample tests and look at which ones fail, or call invoice twice with the same lines and print both results.
  2. Find the line where the first wrong value appears. Copy the file into a project and run a small script under node inspect or with node --inspect-brk and Chrome DevTools; put a breakpoint inside lineAmount and watch line.unitPaise during the first call and the second one.
  3. Fix it so that every sample test passes, without changing what one call of invoice returns.

Starter code · invoice.mjs

/** Tax added to every invoice, as a fraction of the subtotal. */
export const TAX_RATE = 0.18;

/**
 * The amount of one invoice line in paise: quantity times unit price, after the line's discount.
 * @param {{ sku: string, qty: number, unitPaise: number, discountPercent?: number }} line
 * @returns {number}
 */
function lineAmount(line) {
  if (line.discountPercent) {
    line.unitPaise = Math.round(line.unitPaise * (1 - line.discountPercent / 100));
  }
  return line.qty * line.unitPaise;
}

/**
 * The totals of an invoice, in paise.
 * @param {{ sku: string, qty: number, unitPaise: number, discountPercent?: number }[]} lines
 * @returns {{ subtotal: number, tax: number, total: number }}
 */
export function invoice(lines) {
  let subtotal = 0;
  for (const line of lines) {
    subtotal += lineAmount(line);
  }
  const tax = Math.round(subtotal * TAX_RATE);
  return { subtotal, tax, total: subtotal + tax };
}
The sample tests · invoice.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { invoice } from './invoice.mjs';

const lines = () => [
  { sku: 'TEA-250', qty: 2, unitPaise: 12000, discountPercent: 10 },
  { sku: 'MUG-01', qty: 1, unitPaise: 34900 },
];

test('a line without a discount: qty times price, plus 18 % tax', () => {
  assert.deepEqual(invoice([{ sku: 'MUG-01', qty: 3, unitPaise: 34900 }]), { subtotal: 104700, tax: 18846, total: 123546 });
});

test('a discounted line is charged at the discounted price', () => {
  assert.deepEqual(invoice(lines()), { subtotal: 56500, tax: 10170, total: 66670 });
});

test('the same lines give the same invoice every time', () => {
  const same = lines();
  const first = invoice(same);
  assert.deepEqual(invoice(same), first, 'the second preview of the same lines must not change');
});

test('invoice leaves the lines it was given unchanged', () => {
  const given = lines();
  invoice(given);
  assert.deepEqual(given, lines());
});

test('an empty invoice is all zeros', () => {
  assert.deepEqual(invoice([]), { subtotal: 0, tax: 0, total: 0 });
});
A hint

Watch the input, not only the result: compare lines[0].unitPaise before the first call and after it. A function that receives an object can change that object for its caller too, so the second call starts from a price that the first call already discounted.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

7 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 7 Put the steps of the debugging method in order.

    Give each item its position, from 1 (first).

    Show the answer to question 1

    Answer:

    1. Reproduce the failure with an input you can run again
    2. Shrink the input and the code until nothing more can go
    3. Form one specific hypothesis about the cause
    4. Test the hypothesis with an observation that could disprove it
    5. Fix the cause and keep the failing input as a test

    A bug you cannot trigger cannot be checked as fixed, so reproducing comes first, and shrinking makes the rest fast. A hypothesis is only useful when an observation could prove it wrong; when one is disproved you form the next, and when one survives you fix the cause and keep the input as a regression test.

  2. Question 2 of 7 A loop runs 10,000 times and goes wrong only for an item whose qty is 0. What pauses the program at that item and nowhere else?

    Choose one answer.

    Show the answer to question 2

    Answer: A conditional breakpoint inside the loop with the condition item.qty === 0

    A conditional breakpoint pauses only when its expression is true, so it skips the 9,999 passes that do not matter. A line breakpoint pauses on every pass, a logpoint never pauses, and a debugger statement before the loop pauses once, before any item is processed.

  3. Question 3 of 7 In the recorded session of shop.mjs, which observation reveals the cause of the wrong total?

    Choose one answer.

    Show the answer to question 3

    Answer: At line 10, percentOff is 2850 while discount is 5000, so the cap was used as a minimum

    The percentage gives 28.50, below the 50.00 cap, so the discount should be 28.50; the watch expressions show the code chose 50.00, the larger value, which points at Math.max on line 9. The other observations are correct values: they rule hypotheses out but do not show the fault.

  4. Question 4 of 7 What does a debugger; statement do for a visitor whose browser developer tools are closed?

    Choose one answer.

    Show the answer to question 4

    Answer: Nothing at all

    ECMA-262 lets the statement pause only when a debugger is present and active; otherwise it has no observable effect. It still should not ship, which is why ESLint's recommended no-debugger rule reports it.

  5. Question 5 of 7 You need to debug a Node.js service on a remote server. Which way is safe?

    Choose one answer.

    Show the answer to question 5

    Answer: Start it with --inspect on the default 127.0.0.1:9229 and forward the port with ssh -L to your machine

    Anyone who can connect to the inspector can run code inside the process, and the Node.js documentation warns that binding it to a public address allows remote code execution. An SSH tunnel keeps the port local to the server and lets you attach to it as if it were on your machine.

  6. Question 6 of 7 A Node.js stack trace points at dist/app.min.mjs, line 1, column 5310, and the build wrote dist/app.min.mjs.map. What gives you a line of your own source?

    Choose one answer.

    Show the answer to question 6

    Answer: Running the program with --enable-source-maps, so that Node.js applies the map to stack traces

    The source map records where each generated position came from, and Node.js uses it for stack traces only when asked: with --enable-source-maps, or module.setSourceMapsSupport(true) before the code loads. Formatting the file makes it readable but its lines still are not your source's lines.

  7. Question 7 of 7 In the exercise's invoice.mjs, which line makes the second preview of the same lines come out smaller?

    Read the code, then choose one answer.

    function lineAmount(line) {
      if (line.discountPercent) {
        line.unitPaise = Math.round(line.unitPaise * (1 - line.discountPercent / 100));
      }
      return line.qty * line.unitPaise;
    }
    
    export function invoice(lines) {
      let subtotal = 0;
      for (const line of lines) {
        subtotal += lineAmount(line);
      }
      const tax = Math.round(subtotal * TAX_RATE);
      return { subtotal, tax, total: subtotal + tax };
    }
    Show the answer to question 7

    Answer: The assignment to line.unitPaise, which writes the discounted price into the caller's object

    The first call stores the discounted price in the very object the caller passed, so the second call discounts an already discounted price. A watch on line.unitPaise shows it: 12000 before the first call, 10800 after it. Computing the price in a local variable fixes it without changing what one call returns.

Interview questions

Fresher · What does the debugger statement do, and when does it pause the program?

debugger; is a statement that asks the engine to pause at that point, like a breakpoint written into the code. ECMA-262 leaves the effect to the implementation and says that without a debugger present and active it has no observable effect. So in a browser it pauses only while the developer tools are open, and in Node.js only when an inspector client is attached, for example after node --inspect-brk or under node inspect. While paused, you can look at variables, the call stack and watch expressions, and step on line by line. It is useful when setting a breakpoint in the tools is awkward, such as in code that is generated or loaded late. It must not ship: ESLint’s recommended no-debugger rule reports it, and a breakpoint set in the tools or a logpoint does the same job without changing the file.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    About 1 in 500 checkouts in production shows a total that is a few rupees off, and nobody can reproduce it on a laptop. Walk through how you would find the bug.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    To debug a staging service in a container, a teammate starts Node.js with --inspect=0.0.0.0:9229 and publishes the port so that their laptop can attach. What is the risk, and how would you debug that service safely?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Reproduce, shrink, form one hypothesis, test it, then fix the cause and keep the failing input as a test; git bisect finds the commit that introduced a regression.
  • A line breakpoint pauses on a line, a conditional breakpoint only when its expression is true, and a logpoint prints without pausing; the Scope, Watch and Call Stack panes show the state at the pause.
  • node --inspect-brk with chrome://inspect gives Node.js programs the DevTools debugger; node inspect is the terminal version.
  • The inspector listens on 127.0.0.1:9229; never bind it to a public address, and use an SSH tunnel to debug a remote machine.
  • Source maps, standardised as ECMA-426, lead from minified positions to your source; Node.js needs --enable-source-maps or module.setSourceMapsSupport(true).
  • debugger; pauses only when a debugger is attached; no-debugger keeps it out of shipped code.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.