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 1 – Start here: what JavaScript is and how to run it

The console and reading error messages

Use console.log, table, group, count and assert well, read the name, message and stack trace of an error, and see why engines word the same error differently.

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

What you will learn

  • Use console.log, table, group, count, assert and error for the job each does best
  • Read an error's name, message and stack trace to find the line that failed
  • Explain why code and tests should check an error's type rather than its message, which differs between engines

Before you start

What you’ll learn · Before you start

What you will learn

  • Use console.log, table, group, count, assert and error for the job each does best
  • Read an error's name, message and stack trace to find the line that failed
  • Explain why code and tests should check an error's type rather than its message, which differs between engines

Before you start

On this page

When a program goes wrong, two things tell you what happened: what it printed, and the errors it threw. The console is how a program prints for its developer. console.log is the method everyone knows, but table, group, count, assert and error each make a particular job easier. An error is an object with a name that says what kind of problem it is (TypeError, ReferenceError …), a message that says what went wrong, and a stack trace that says where: the chain of function calls that led to the failing line. This lesson prints with the console methods that earn their place, reads a stack trace from the top, and shows something that catches out many tests: two engines word the same error differently, so code should check an error’s type, never its message.

The console methods worth knowing

console is not part of ECMAScript. It is a host API, specified by the WHATWG Console Standard and provided by browsers, Node.js and the Run buttons of this track. The methods you will use most:

Method What it does Use it for
console.log(a, b, …) Prints its arguments, separated by spaces; objects are formatted Everyday output
console.error(…), console.warn(…) Like log, marked as an error or a warning; in Node.js they go to standard error Problems, kept apart from normal output
console.table(rows) Prints an array of objects as a table, one row per item Comparing records at a glance
console.group(label), console.groupEnd() Indents everything logged between them Output of a nested step
console.count(label) Prints how many times it was called with that label Checking how often a branch runs
console.assert(condition, …) Prints “Assertion failed” and its message when the condition is false; does not stop the program A check that should never fail
console.time(label), console.timeEnd(label) Prints the time between the two calls Rough timings only
console.trace(…) Prints a message with the current call stack Finding out who called a function

Here are most of them at work on the results of one test run:

A tour of the console JavaScript · console-tour.mjs
// The console methods worth knowing, on the results of one CI run.
const jobs = [
  { job: 'lint', seconds: 12, passed: true },
  { job: 'unit tests', seconds: 48, passed: true },
  { job: 'e2e tests', seconds: 230, passed: false },
];

console.log('jobs:', jobs.length, 'first:', jobs[0]);
console.table(jobs);

console.group('failed jobs');
for (const j of jobs.filter((x) => !x.passed)) console.log(`${j.job} after ${j.seconds} s`);
console.groupEnd();

for (const j of jobs) console.count(j.passed ? 'passed' : 'failed');

console.assert(jobs.every((j) => j.passed), 'not every job passed');
console.log('the program goes on after a failed assert');
console.error('error: e2e tests failed');

Output

jobs: 3 first: { job: 'lint', seconds: 12, passed: true }
┌─────────┬──────────────┬─────────┬────────┐
│ (index) │ job          │ seconds │ passed │
├─────────┼──────────────┼─────────┼────────┤
│ 0       │ 'lint'       │ 12      │ true   │
│ 1       │ 'unit tests' │ 48      │ true   │
│ 2       │ 'e2e tests'  │ 230     │ false  │
└─────────┴──────────────┴─────────┴────────┘
failed jobs
  e2e tests after 230 s
passed: 1
passed: 2
failed: 1
the program goes on after a failed assert

Printed as an error (standard error)

Assertion failed: not every job passed
error: e2e tests failed

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

Version note

The Run button’s console prints the same lines, but it centres the cells of console.table, the way older versions of Node.js laid tables out, where Node.js 24 aligns them on the left.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints:

jobs: 3 first: { job: 'lint', seconds: 12, passed: true }
┌─────────┬──────────────┬─────────┬────────┐
│ (index) │     job      │ seconds │ passed │
├─────────┼──────────────┼─────────┼────────┤
│    0    │    'lint'    │   12    │  true  │
│    1    │ 'unit tests' │   48    │  true  │
│    2    │ 'e2e tests'  │   230   │ false  │
└─────────┴──────────────┴─────────┴────────┘
failed jobs
  e2e tests after 230 s
passed: 1
passed: 2
failed: 1
the program goes on after a failed assert

Printed as an error (standard error):

Assertion failed: not every job passed
error: e2e tests failed

Two lines in that output deserve a second look. console.assert printed Assertion failed: not every job passed and the program carried on: the Console Standard defines assert as a logger, not a check that throws, and Node.js documents that it “does not otherwise affect execution”. And the assert and console.error lines appear on standard error, which a terminal shows mixed with the other output but a script can capture apart.

The console also behaves differently in a browser and in Node.js. Node.js turns every value into text at the moment of the call. A browser’s developer tools show an object you can expand, and MDN warns that its content is read lazily, when you first look at it: log an object, change it, then expand the log entry, and you see the changed object. When you need a snapshot, log a copy, such as structuredClone(obj), or the parts you care about.

The browser’s alert(), confirm() and prompt() show a dialog and stall the page until it is closed, by the HTML Standard’s own description, so they make poor debugging tools. This track prints with the console and changes the page with the DOM.

Reading an error message and its stack trace

This program prints a label for each order. The second order has no customer, so reading the customer’s name throws, three function calls deep, and nothing catches the error:

An error nobody catches JavaScript · stack-trace.mjs
// An order without its customer: reading the customer's name throws, three calls deep.
function customerName(order) {
  return order.customer.name;
}

function label(order) {
  return `${order.id}: ${customerName(order)}`;
}

function printLabels(orders) {
  for (const order of orders) console.log(label(order));
}

printLabels([{ id: 'A-1', customer: { name: 'Asha' } }, { id: 'A-2' }]);

Output (exit status 1)

A-1: Asha

Printed as an error (standard error)

stack-trace.mjs:3
  return order.customer.name;
                        ^

TypeError: Cannot read properties of undefined (reading 'name')
    at customerName (stack-trace.mjs:3:25)
    at label (stack-trace.mjs:7:26)
    at printLabels (stack-trace.mjs:11:43)
    at stack-trace.mjs:14:1
    at ModuleJob.run (node:internal/modules/esm/module_job:561:25)
    at async node:internal/modules/esm/loader:647:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v24.21.0

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

Node.js printed the first label, then stopped with the error. Read it from the top:

  1. The source line and a caret. stack-trace.mjs:3 names the file and line, and the ^ marks the expression that failed: .name, read from order.customer, which is undefined.
  2. The name and message. TypeError says the operation was not possible on that kind of value; Cannot read properties of undefined (reading 'name') says which operation.
  3. The stack, innermost call first. Each at line is one function call, with its file, line and column: customerName failed on line 3, called by label on line 7, called by printLabels on line 11, called from the top level of the file on line 14. The last frames, node:internal/…, are Node.js’s own code that loaded the module; you can stop reading when they start.

The first frame in your own code is usually where the fix belongs, but not always. Here the bug may be the order without a customer, which came from elsewhere; the stack shows the path the bad value travelled, so you can choose where to handle it.

Version note

QuickJS words the message as cannot read property 'name' of undefined, prints no source line or caret, names the top level <anonymous>, and counts columns from a different point. The order of the frames is the same.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints (exit status 1):

A-1: Asha

Printed as an error (standard error):

TypeError: cannot read property 'name' of undefined
    at customerName (stack-trace.mjs:3:24)
    at label (stack-trace.mjs:7:38)
    at printLabels (stack-trace.mjs:11:48)
    at <anonymous> (stack-trace.mjs:14:12)

The name of an error is its class, and ECMA-262 defines the classes an engine throws:

Name ECMA-262 uses it for A typical cause
TypeError An operation that cannot work on the value it was given Reading a property of undefined, calling something that is not a function
ReferenceError An invalid reference A misspelt variable name, a let used before its line
RangeError A value outside the allowed range new Array(-1), toFixed(200)
SyntaxError Source text that cannot be parsed A missing bracket; JSON.parse of invalid JSON
URIError A URI function used against its definition decodeURIComponent('%')
AggregateError Several errors at once Promise.any when every promise rejects
Error Anything else, and the base class of all of them throw new Error('…') in your own code

EvalError also exists, but the specification no longer throws it and keeps it only for compatibility.

The same mistake in two engines

A message is meant for people, and each engine writes its own. This program makes seven common mistakes and prints the name and message of each error:

Seven mistakes, their names and messages JavaScript · same-error-two-engines.mjs
// Seven common mistakes. Each one throws; the program prints the error's name and message.
const mistakes = {
  'read a property of null': () => {
    const order = null;
    return order.total;
  },
  'read a property of undefined': () => {
    const order = {};
    return order.customer.name;
  },
  'call something that is not a function': () => {
    const order = { total: 5 };
    return order.total();
  },
  'use a name that was never declared': () => totl + 1,
  'use a let variable before its line': () => {
    const copy = later;
    let later = 1;
    return copy;
  },
  'assign to a constant': () => {
    const n = 1;
    n = 2;
    return n;
  },
  'make an array of length -1': () => new Array(-1),
};

for (const [mistake, run] of Object.entries(mistakes)) {
  try {
    run();
  } catch (error) {
    console.log(`${mistake}\n  ${error.name}: ${error.message}`);
  }
}

Output

read a property of null
  TypeError: Cannot read properties of null (reading 'total')
read a property of undefined
  TypeError: Cannot read properties of undefined (reading 'name')
call something that is not a function
  TypeError: order.total is not a function
use a name that was never declared
  ReferenceError: totl is not defined
use a let variable before its line
  ReferenceError: Cannot access 'later' before initialization
assign to a constant
  TypeError: Assignment to constant variable.
make an array of length -1
  RangeError: Invalid array length

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node same-error-two-engines.mjs

Version note

QuickJS, the engine of the Run button, throws the same seven error types with other words: cannot read property 'total' of null, not a function, 'totl' is not defined, later is not initialized, 'n' is read-only and invalid array length.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints:

read a property of null
  TypeError: cannot read property 'total' of null
read a property of undefined
  TypeError: cannot read property 'name' of undefined
call something that is not a function
  TypeError: not a function
use a name that was never declared
  ReferenceError: 'totl' is not defined
use a let variable before its line
  ReferenceError: later is not initialized
assign to a constant
  TypeError: 'n' is read-only
make an array of length -1
  RangeError: invalid array length

Every mistake produced the same error name in both engines, and every message differs, if only by a quote mark or a capital letter. Browsers differ in the same way. MDN’s error reference lists the wording of each family of engines; three of the mistakes above read like this:

Mistake V8 (Chrome, Node.js) Firefox Safari
Read a property of undefined Cannot read properties of undefined (reading 'x') undefined has no properties undefined is not an object (evaluating 'undefined.x')
Use a name never declared x is not defined x is not defined Can't find variable: x
Use a let before its line Cannot access 'X' before initialization can't access lexical declaration 'X' before initialization Cannot access uninitialized variable.

So the message is for reading, and the name is for code. A test that compares error.message with V8’s wording passes on Node.js and fails in Safari, in Firefox and in the Run button, although the code behaves the same in all of them.

Catching an error and checking what it is

try … catch hands you the error object, and its standard properties are name and message, plus cause when the code that threw it gave one. The stack property is not part of ECMA-262 at all: engines add it, each in its own format, as the next run shows.

What a caught error carries JavaScript · catch-and-inspect.mjs
// A function that throws on bad input, and a caller that looks at what it caught.
function parsePrice(text) {
  const value = Number(text);
  if (!Number.isFinite(value)) throw new RangeError(`not a price: ${JSON.stringify(text)}`);
  return value;
}

try {
  parsePrice('12,50');
} catch (error) {
  console.log('instanceof RangeError:', error instanceof RangeError);
  console.log('name:', error.name);
  console.log('message:', error.message);
  console.log('stack:');
  console.log(error.stack);
}

Output

instanceof RangeError: true
name: RangeError
message: not a price: "12,50"
stack:
RangeError: not a price: "12,50"
    at parsePrice (catch-and-inspect.mjs:4:38)
    at catch-and-inspect.mjs:9:3
    at ModuleJob.run (node:internal/modules/esm/module_job:561:25)
    at async node:internal/modules/esm/loader:647:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

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

Version note

QuickJS starts stack with the frames themselves, without the line that repeats the name and message, and it counts columns from another point, so the same program prints a shorter stack with other numbers. name and message are identical.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints:

instanceof RangeError: true
name: RangeError
message: not a price: "12,50"
stack:
    at parsePrice (catch-and-inspect.mjs:4:52)
    at <anonymous> (catch-and-inspect.mjs:9:13)

The message here came from our own code, so it is the same everywhere; only engine-made messages vary. To decide what to do with an error, check its class with instanceof (error instanceof RangeError) or its name. Your own errors can carry a code property or a class of their own, which a later lesson builds. And Error.isError(value), new in ECMAScript 2026, tells you whether a value is a real error object at all, even one from another realm such as an iframe, where instanceof gives a wrong false; Node.js 24 already has it.

JSON Formatter When an error says JSON.parse failed, paste the text here to see where the JSON breaks.

Exercise: fix the TypeError

Old orders sometimes arrive without a customer or without an address, and shippingLabel throws on them. Read the error the sample tests report, find the expression that fails, and make the function print placeholders instead.

Exercise · Easy · JavaScript

Fix the TypeError in a parcel label

shippingLabel(order) builds the line printed on a parcel, such as "A-1 · Asha · Pune": the order id, the customer's name and the customer's city. Orders from an old system sometimes arrive without a customer, or with a customer but no address, and then the function stops the whole print run with

TypeError: Cannot read properties of undefined (reading 'name')

Change it so that it never throws for these orders: a missing name prints as unknown customer and a missing city as no city. Keep everything else as it is, and keep a name or city that is present, even an empty one, as given.

Starter code · label.mjs

/**
 * The line printed on a parcel: order id, customer name and city, separated by " · ".
 * @param {{ id: string, customer?: { name?: string, address?: { city?: string } } }} order
 * @returns {string}
 */
export function shippingLabel(order) {
  return `${order.id} · ${order.customer.name} · ${order.customer.address.city}`;
}
The sample tests · label.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { shippingLabel } from './label.mjs';

test('a complete order prints id, name and city', () => {
  assert.equal(shippingLabel({ id: 'A-1', customer: { name: 'Asha', address: { city: 'Pune' } } }), 'A-1 · Asha · Pune');
});

test('an order without a customer does not throw', () => {
  assert.equal(shippingLabel({ id: 'A-2' }), 'A-2 · unknown customer · no city');
});

test('a customer without an address keeps the name', () => {
  assert.equal(shippingLabel({ id: 'A-3', customer: { name: 'Ravi' } }), 'A-3 · Ravi · no city');
});

test('an empty city is a value the customer gave, so it stays', () => {
  assert.equal(shippingLabel({ id: 'A-4', customer: { name: 'Meera', address: { city: '' } } }), 'A-4 · Meera · ');
});
A hint

The error names the property that could not be read, name, and the line shows what was undefined: order.customer. Optional chaining, order.customer?.name, gives undefined instead of throwing when customer is missing, and ?? replaces only null and undefined with a default, so an empty string stays as it is (|| would replace it too).

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.

Check yourself

6 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 6 console.assert(total > 0, "total must be positive") runs while total is 0. What happens?

    Choose one answer.

    Show the answer to question 1

    Answer: It prints "Assertion failed: total must be positive" and the program continues

    The Console Standard defines assert as a logger: when the condition is false it logs "Assertion failed" with the message, and Node.js documents that it does not otherwise affect execution. To stop a program, throw an error, or use an assertion library such as node:assert.

  2. Question 2 of 6 This error came from a shop's code. Which line should you open first?

    Read the code, then choose one answer.

    TypeError: Cannot read properties of undefined (reading 'price')
        at lineTotal (cart.mjs:12:28)
        at Array.map (<anonymous>)
        at cartTotal (cart.mjs:20:30)
        at checkout (pay.mjs:8:15)
    Show the answer to question 2

    Answer: cart.mjs, line 12, where lineTotal read .price from undefined

    Frames are listed innermost first. The top frame is where the error was thrown: lineTotal, at line 12, column 28 of cart.mjs. The frames below it show how the program got there, which helps when the bad value came from a caller, here an item without a price passed through map from cartTotal.

  3. Question 3 of 6 A test must check that loadConfig() fails with a TypeError when it gets null. Which check works in every engine?

    Choose one answer.

    Show the answer to question 3

    Answer: error instanceof TypeError, or error.name === "TypeError"

    Error classes and names come from ECMA-262 and are the same everywhere. Messages are each engine's own wording, as the seven mistakes showed, and stack is not standard at all: QuickJS does not even start it with the name.

  4. Question 4 of 6 In Node.js, which of these print to standard error rather than standard output?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • console.warn
    • console.error

    Node.js's global console sends error and warn to process.stderr, and log, info, table and the others to process.stdout. A script can capture the two streams separately, for example to keep problems out of a report.

  5. Question 5 of 6 In a browser, you log an object, then your code changes one of its properties, and then you expand the logged object in the console. What do you see?

    Choose one answer.

    Show the answer to question 5

    Answer: The changed value, because browsers read a logged object's content when you first expand it

    MDN notes that information about a logged object is retrieved lazily, when it is first viewed. Log a copy, such as structuredClone(obj), when you need the state at that moment. Node.js formats values as text when console.log runs, so it shows the state at the time of the call.

  6. Question 6 of 6 Which error does new Array(-1) throw?

    Choose one answer.

    Show the answer to question 6

    Answer: RangeError

    -1 is outside the range of valid array lengths, so the engine throws a RangeError: Invalid array length in Node.js and invalid array length in QuickJS. The class is the same in both; the wording is not.

Interview questions

Fresher · What do the name, the message and the stack of a JavaScript error tell you?

The name is the error’s class, such as TypeError, ReferenceError or RangeError, and says what kind of problem it is; ECMA-262 defines these classes, so the name is the same in every engine. The message says what exactly went wrong, in words chosen by whoever threw it: your own code, or the engine, whose wording differs between V8, SpiderMonkey, JavaScriptCore and QuickJS. The stack, which engines add although ECMA-262 does not define it, lists the chain of calls that led to the error, innermost first, with file, line and column; the first frame in your own code is where to start looking. In practice: read the message to understand, read the stack to locate, and in code or tests branch on the name or the class, with instanceof, never on the message text.

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

  1. Fresher to mid

    A test asserts that err.message equals "Cannot read properties of null (reading 'path')". It passes on CI with Node.js, but fails when the same suite runs in Safari and in an embedded engine. What is wrong with the test, and how should the code and the test identify this failure instead?

    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

    Production error reports show "TypeError: Cannot read properties of undefined (reading 'map')" with a stack whose top frame points to app.min.js, line 1, column 48211. Walk through how you find the real cause and fix it.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • console comes from the host, specified by the WHATWG Console Standard. table, group, count and assert save time; error and warn go to standard error in Node.js; assert never stops the program.
  • Browsers read logged objects lazily, when you expand them; Node.js prints text at the moment of the call.
  • Read an error from the top: the line and caret, then the name and message, then the stack, innermost call first, until Node.js’s own frames start.
  • Error names come from ECMA-262 and match in every engine; messages and the stack format do not. Check the class or the name, never the message text.

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.