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.
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:
// 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
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 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
Node.js printed the first label, then stopped with the error. Read it from the top:
- The source line and a caret.
stack-trace.mjs:3names the file and line, and the^marks the expression that failed:.name, read fromorder.customer, which isundefined. - The name and message.
TypeErrorsays the operation was not possible on that kind of value;Cannot read properties of undefined (reading 'name')says which operation. - The stack, innermost call first. Each
atline is one function call, with its file, line and column:customerNamefailed on line 3, called bylabelon line 7, called byprintLabelson 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 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
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.
// 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
Runs on this device, in your browser. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
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.
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).
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
-
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?
-
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.
Key takeaways
consolecomes from the host, specified by the WHATWG Console Standard.table,group,countandassertsave time;errorandwarngo to standard error in Node.js;assertnever 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
stackformat do not. Check the class or the name, never the message text.
References
- Console Standard (WHATWG)
- console (Web APIs) (MDN Web Docs)
- Node.js 24 documentation: Console (OpenJS Foundation (Node.js))
- Node.js 24 documentation: Errors (OpenJS Foundation (Node.js))
- ECMAScript 2026 Language Specification: Error objects (Ecma International)
- ECMAScript 2026 Language Specification: Native error types used in this standard (Ecma International)
- TypeError: null/undefined has no properties (MDN Web Docs)
- ReferenceError: "x" is not defined (MDN Web Docs)
- ReferenceError: can't access lexical declaration 'X' before initialization (MDN Web Docs)
- HTML Standard: user prompts (alert, confirm, prompt) (WHATWG)
- Error.isError proposal (Ecma International (TC39))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress