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

Built-in error types and engine messages

TypeError, RangeError, ReferenceError, SyntaxError, URIError, AggregateError: what throws each, which to throw yourself, and checks that work in every engine.

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

What you will learn

  • Recognise the operations that throw TypeError, RangeError, ReferenceError, SyntaxError and URIError
  • Choose the built-in error class that a function of your own should throw
  • Write checks and tests that rely on an error's class or code, never on an engine's message
  • Identify Node.js errors by their code and web API errors by their DOMException name

Before you start

What you’ll learn · Before you start

What you will learn

  • Recognise the operations that throw TypeError, RangeError, ReferenceError, SyntaxError and URIError
  • Choose the built-in error class that a function of your own should throw
  • Write checks and tests that rely on an error's class or code, never on an engine's message
  • Identify Node.js errors by their code and web API errors by their DOMException name

Before you start

On this page

JavaScript reports a failure with an object of the Error family, and the object’s class says what kind of failure it was. ECMA-262 defines the classes that engines throw: TypeError when an operation cannot work on the value it was given, RangeError when a value is outside the allowed set or range, ReferenceError when a name cannot be resolved, SyntaxError when text cannot be parsed, URIError when the URI functions are misused, and AggregateError for several failures at once; EvalError remains only for compatibility. Hosts add their own: web APIs throw a DOMException identified by its name, and Node.js puts a stable code on its errors. Your functions should throw the same classes, chosen by the same rules, so that their callers can tell a bug from bad input. The class is meant for code. The message is meant for people, and it changes between engines and between releases.

The family and what each class means

Class ECMA-262’s meaning Your own code throws it when
TypeError An operation failed and none of the other classes fits An argument is the wrong kind of value: text for a number, null for an object, something that is not a function
RangeError A value is not in the set or range of allowed values A number is too big, too small or not whole, or a string is not one of the accepted choices
ReferenceError An invalid reference was found Never: it describes the program’s own names
SyntaxError Text could not be parsed Text in a format your code parses cannot be read
URIError A global URI function was used against its definition Rarely: it belongs to encodeURI and its relatives
AggregateError Several errors are reported together One check finds several problems, such as several invalid fields of a form
EvalError No longer used by the specification Never

The classes share one design. Each has its own prototype, which inherits from Error.prototype, and the name lives on that prototype rather than on each error. String(error) uses the inherited Error.prototype.toString, which joins the name and the message with a colon. All of them can be called with or without new.

What the built-in errors share JavaScript · family.mjs
// What every built-in error shares: Error.prototype in its chain, a name on its own prototype, and toString().
const classes = [TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError, AggregateError];
for (const C of classes) {
  const error = C === AggregateError ? new C([], 'two checks failed') : new C('two checks failed');
  console.log(
    `${C.name.padEnd(14)} instanceof Error: ${error instanceof Error}, name from the prototype: ${!Object.hasOwn(error, 'name')}, ${String(error)}`,
  );
}
const withoutNew = RangeError('no new needed');
console.log(withoutNew instanceof RangeError, withoutNew.message);

Output

TypeError      instanceof Error: true, name from the prototype: true, TypeError: two checks failed
RangeError     instanceof Error: true, name from the prototype: true, RangeError: two checks failed
ReferenceError instanceof Error: true, name from the prototype: true, ReferenceError: two checks failed
SyntaxError    instanceof Error: true, name from the prototype: true, SyntaxError: two checks failed
URIError       instanceof Error: true, name from the prototype: true, URIError: two checks failed
EvalError      instanceof Error: true, name from the prototype: true, EvalError: two checks failed
AggregateError instanceof Error: true, name from the prototype: true, AggregateError: two checks failed
true no new needed

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

What makes the engine throw each one

Twenty operations that fail, sorted by the class the engine throws:

Twenty failures and their classes JavaScript · error-zoo.mjs
// Operations that fail, grouped by the error class the engine throws for them. Only the class is printed: the
// messages are each engine's own words.
const cases = [
  ['call a number as a function', () => (42)()],
  ['read a property of undefined', () => undefined.total],
  ['use new on an arrow function', () => new (() => {})()],
  ['call a class without new', () => { class Cart {} Cart(); }],
  ['add a BigInt to a number', () => 10n + 1],
  ['assign to a frozen property', () => { Object.freeze({ qty: 1 }).qty = 2; }],
  ['stringify a circular object', () => { const a = {}; a.self = a; JSON.stringify(a); }],
  ['loop over a number with for…of', () => { for (const x of 7) console.log(x); }],
  ['make an array of length -1', () => new Array(-1)],
  ['round to 101 digits with toFixed', () => (1.5).toFixed(101)],
  ['repeat a string -1 times', () => 'ab'.repeat(-1)],
  ['turn 1.5 into a BigInt', () => BigInt(1.5)],
  ['ask for an invalid date as ISO text', () => new Date(NaN).toISOString()],
  ['read a name that was never declared', () => totl],
  ['read a let before its line', () => { later; let later = 1; }],
  ['parse invalid JSON', () => JSON.parse('{"qty": 2,}')],
  ['build a regex from "["', () => new RegExp('[')],
  ['compile a function body with new Function', () => new Function('return {')],
  ['decode the text "%"', () => decodeURIComponent('%')],
  ['encode a lone surrogate', () => encodeURIComponent('\uD800')],
];

for (const [what, operation] of cases) {
  try {
    operation();
    console.log(`${'no error'.padEnd(15)} ${what}`);
  } catch (error) {
    console.log(`${error.constructor.name.padEnd(15)} ${what}`);
  }
}

Output

TypeError       call a number as a function
TypeError       read a property of undefined
TypeError       use new on an arrow function
TypeError       call a class without new
TypeError       add a BigInt to a number
TypeError       assign to a frozen property
TypeError       stringify a circular object
TypeError       loop over a number with for…of
RangeError      make an array of length -1
RangeError      round to 101 digits with toFixed
RangeError      repeat a string -1 times
RangeError      turn 1.5 into a BigInt
RangeError      ask for an invalid date as ISO text
ReferenceError  read a name that was never declared
ReferenceError  read a let before its line
SyntaxError     parse invalid JSON
SyntaxError     build a regex from "["
SyntaxError     compile a function body with new Function
URIError        decode the text "%"
URIError        encode a lone surrogate

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

The output is the same in Node.js and in the Run button’s QuickJS engine, line for line, because the class of each error is fixed by the standard. A few of the lines deserve a closer look.

  • TypeError is the widest class. ECMA-262 uses it for every failed operation that no other class describes, so it covers calling something that is not a function, reading a property of undefined or null, breaking the rules of new and classes, mixing BigInt with ordinary numbers, and iterating a value that is not iterable.
  • Writing to a frozen property throws only in strict code. Modules are always strict, so the example threw. In a classic script without 'use strict' the same assignment is skipped without a word, which hides more bugs than it prevents.
  • RangeError guards lengths, counts and digits. The value had the right type, a number, but not an allowed one: a negative array length, 101 decimals, a repeat count of −1, a fraction where BigInt needs a whole number.
  • SyntaxError comes in two kinds. Text that the engine parses while your code runs, through JSON.parse, new RegExp or new Function, throws a SyntaxError that a catch can handle. A syntax error in a script itself is found before any of the script runs, so nothing in that script can catch it.
  • Engine limits are the exception to the rule. When recursion goes too deep, V8, the engine of Chrome and Node.js, throws a RangeError with the message “Maximum call stack size exceeded”, while Firefox throws an InternalError, a class of its own that no standard defines. Code that must react to a stack overflow everywhere cannot rely on one class.

Choosing the class your function throws

When your own function rejects an argument, throw the class that describes the mistake. The order of the questions matters: check the kind of value first, then whether the value is allowed, then the rules of your application.

Five questions in order for picking the error your function throws: TypeError, RangeError, SyntaxError, your own class, or Error.1. The wrong kind of value?text for a number, null for an objectyes: TypeError2. The right kind, but not allowed?a quantity of 0, an unknown statusyes: RangeError3. Your own format that won't parse?yes: SyntaxError,or a parse error class of your own4. A rule of your application broken?the seat is taken, the balance too lowyes: an error class of your own5. Anything else:an Error whose message names the valuenononono

Which error should your function throw?

Text description of the diagram

Ask the questions from the top; the first one answered "yes" decides.

1. Is the argument the wrong kind of value, such as text where a number belongs or `null` where an object belongs? Then throw a `TypeError`. 2. Is it the right kind of value but not one of the allowed ones, such as a quantity of 0 or an unknown status? Then throw a `RangeError`. 3. Is it text in a format your own code parses, and it cannot be parsed? Then throw a `SyntaxError`, or a parse error class of your own. 4. Is a rule of your application broken, such as a seat that is already taken or a balance that is too low? Then throw an error class of your own. 5. Anything else: throw an `Error` whose message names the value that caused it.

The difference between the first two questions is the one people blur most often. A TypeError says the caller passed the wrong kind of thing, which usually means the calling code is wrong. A RangeError says the value was the right kind but not acceptable, which often means a person made a choice that has to change. ECMA-262 defines RangeError for values outside a “set or range” of allowed values, so an unknown status string is a RangeError as much as a quantity of 0 is; Intl.NumberFormat, for one, throws it for a currency code that is not three letters.

Two classes, two reactions JavaScript · choose-type.mjs
// Two kinds of mistake, two classes: the wrong kind of value is a TypeError, a value outside the allowed ones is a
// RangeError. The caller treats them differently.
const MAX_PER_ORDER = 10;

function setQuantity(cart, sku, qty) {
  if (typeof qty !== 'number') throw new TypeError(`qty must be a number, got ${typeof qty}`);
  if (!Number.isInteger(qty) || qty < 1 || qty > MAX_PER_ORDER) {
    throw new RangeError(`qty must be a whole number from 1 to ${MAX_PER_ORDER}, got ${qty}`);
  }
  cart.set(sku, qty);
}

const cart = new Map();
for (const qty of [2, 0, 2.5, 25, '3']) {
  try {
    setQuantity(cart, 'TEA-250', qty);
    console.log(`${JSON.stringify(qty).padEnd(5)} -> cart has ${cart.get('TEA-250')}`);
  } catch (error) {
    if (error instanceof RangeError) {
      console.log(`${JSON.stringify(qty).padEnd(5)} -> tell the shopper: ${error.message}`);
    } else {
      console.log(`${JSON.stringify(qty).padEnd(5)} -> a bug in the calling code: ${error.name}`);
    }
  }
}

Output

2     -> cart has 2
0     -> tell the shopper: qty must be a whole number from 1 to 10, got 0
2.5   -> tell the shopper: qty must be a whole number from 1 to 10, got 2.5
25    -> tell the shopper: qty must be a whole number from 1 to 10, got 25
"3"   -> a bug in the calling code: TypeError

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

The caller tells the shopper about every RangeError, because those messages describe a choice the shopper made. The text '3' produced a TypeError instead: a quantity box returns text, and the code that called setQuantity forgot to convert it, which no message to the shopper can fix. Two rules complete the picture. Never throw a ReferenceError or an EvalError yourself, because they describe the program, not its data. And when a rule of your application is broken, such as a seat that is already booked, give the failure an error class of its own, which the next lesson builds; a built-in class would make it look like an ordinary programming mistake.

AggregateError: several failures in one throw

Some checks find more than one problem, and a form that reports its mistakes one at a time makes people submit it again and again. An AggregateError carries a list of errors in its errors property, next to its own message:

Every problem of a form at once JavaScript · aggregate-validation.mjs
// Report every problem of a form at once: one AggregateError that carries an error per field.
function validateSignup(form) {
  const problems = [];
  if (typeof form.name !== 'string' || form.name.trim() === '') problems.push(new TypeError('name is required'));
  if (!/^\d{6}$/.test(form.pin ?? '')) problems.push(new RangeError('pin code must have 6 digits'));
  if (!Number.isInteger(form.age) || form.age < 18) problems.push(new RangeError('age must be a whole number, 18 or more'));
  if (problems.length > 0) throw new AggregateError(problems, `${problems.length} fields need attention`);
  return { ...form, name: form.name.trim() };
}

try {
  validateSignup({ name: ' ', pin: '5600', age: 17.5 });
} catch (error) {
  console.log(`${error.name}: ${error.message}`);
  for (const problem of error.errors) console.log(`  ${problem.name}: ${problem.message}`);
}
console.log(validateSignup({ name: ' Asha ', pin: '560001', age: 34 }).name);

Output

AggregateError: 3 fields need attention
  TypeError: name is required
  RangeError: pin code must have 6 digits
  RangeError: age must be a whole number, 18 or more
Asha

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

The standard library throws one in a single place today: Promise.any rejects with an AggregateError whose errors hold every reason when all of its promises reject. A newer relative, SuppressedError, comes with explicit resource management, the using declaration, which TC39 expects to publish in ECMAScript 2027. It carries the error thrown while a resource was being released in error, and the error that was already on its way in suppressed. Node.js 24 has both; the Run button’s engine has neither.

Messages are written for people

The message says what went wrong in words that the engine chose, and engines choose differently. This check runs the same JSON.parse call twice, once asking for the class and once comparing the message with V8’s wording:

A check on the class and a check on the words JavaScript · message-check.mjs
// Two checks of the same failure. The first asks for the class, which ECMA-262 fixes; the second compares the
// message with the words one engine happens to use.
function check(label, passed) {
  console.log(`${passed ? 'pass' : 'FAIL'}  ${label}`);
}

let error;
try {
  JSON.parse('{"qty": 2,}');
} catch (caught) {
  error = caught;
}

check('the error is a SyntaxError', error instanceof SyntaxError);
check('the message is the one V8 writes', error.message === 'Expected double-quoted property name in JSON at position 10 (line 1 column 11)');
console.log(`message here: ${error.message}`);

Output

pass  the error is a SyntaxError
pass  the message is the one V8 writes
message here: Expected double-quoted property name in JSON at position 10 (line 1 column 11)

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

Version note

QuickJS, the engine of the Run button, also throws a SyntaxError, so the first check passes there too, but its message is expecting property name, so the second check prints FAIL.

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

pass  the error is a SyntaxError
FAIL  the message is the one V8 writes
message here: expecting property name

The wording also moves between releases of one engine. The same JSON.parse('{"qty": 2,}'), run in five releases of Node.js, gave three different messages:

Node.js Message
18.20.2 Unexpected token } in JSON at position 10
20.19.0 Expected double-quoted property name in JSON at position 10
22.23.3, 24.21.0, 26.10.0 Expected double-quoted property name in JSON at position 10 (line 1 column 11)

A test that compared the message would have broken twice in two upgrades, and the Node.js documentation warns that messages may change in any release. The console and reading error messages shows how three browser engines word the same mistakes. In code and in tests, rely on the class, on name, and on a documented code.

JSON Validator When JSON.parse rejects a file, the validator shows where the JSON breaks, with its line and column.

Errors that come from the host

Node.js and browsers throw errors of their own, and each identifies them in its own way.

Node.js codes and DOMException names JavaScript · host-errors.mjs
// Errors from Node.js and from web APIs: identified by code and by name, not by message.
import { readFileSync } from 'node:fs';

function show(label, operation) {
  try {
    operation();
  } catch (error) {
    const kind = error instanceof DOMException ? 'DOMException' : error.constructor.name;
    console.log(`${label}\n  ${kind}, name ${error.name}, code ${JSON.stringify(error.code)}`);
  }
}

show('read a file that does not exist', () => readFileSync('missing-config.json', 'utf8'));
show('make a Buffer from a number', () => Buffer.from(42));
show('make a Buffer of size -1', () => Buffer.alloc(-1));
show('parse a URL that is not one', () => new URL('not a url'));
show('copy a function with structuredClone', () => structuredClone(() => 1));
show('read why an aborted signal stopped', () => { throw AbortSignal.abort().reason; });

const selectorError = new DOMException('"[" is not a valid selector', 'SyntaxError');
console.log(`a DOMException named SyntaxError: instanceof SyntaxError ${selectorError instanceof SyntaxError}, instanceof Error ${selectorError instanceof Error}`);

Output

read a file that does not exist
  Error, name Error, code "ENOENT"
make a Buffer from a number
  TypeError, name TypeError, code "ERR_INVALID_ARG_TYPE"
make a Buffer of size -1
  RangeError, name RangeError, code "ERR_OUT_OF_RANGE"
parse a URL that is not one
  TypeError, name TypeError, code "ERR_INVALID_URL"
copy a function with structuredClone
  DOMException, name DataCloneError, code 25
read why an aborted signal stopped
  DOMException, name AbortError, code 20
a DOMException named SyntaxError: instanceof SyntaxError false, instanceof Error true

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

  • Node.js system errors come from the operating system and carry a code such as ENOENT (no such file or folder), EACCES (permission denied) or EADDRINUSE (the port is taken), plus syscall and, for files, path.
  • Node.js’s own errors are ordinary TypeError, RangeError and Error objects with a code that starts with ERR_, as ERR_INVALID_ARG_TYPE and ERR_OUT_OF_RANGE above. The documentation calls code the most stable way to identify an error: it changes only between major versions.
  • DOMException is the error of web APIs, and Node.js uses it for the same APIs: structuredClone throws a DataCloneError, and an aborted signal’s reason is an AbortError. Its name is the identifier to check; the numeric code is a legacy value that newer names do not have.

One name appears in both worlds. Web APIs that parse text, such as document.querySelector given an invalid selector, throw a DOMException named SyntaxError, and the Web IDL standard warns against confusing it with JavaScript’s SyntaxError. The last line of the example shows the trap: such an error is an Error, but not an instance of SyntaxError. Check error instanceof DOMException && error.name === 'SyntaxError' for the first kind and error instanceof SyntaxError for the second.

Exercise: the right error for each bad booking

Exercise · Easy · JavaScript

Throw the right error for each bad booking

validateBooking(input) checks a request for bus seats before it is sent on, such as { adults: 2, children: 1, seat: 'window' }, and returns it with the number of passengers added. Right now it throws a plain Error for every problem, so the code that calls it cannot tell a bug from a choice the traveller must fix. Make it throw the class that fits each problem:

  • input is not an object (or is null): TypeError;
  • adults or children is not a number: TypeError;
  • adults or children is a number but not a whole number of 0 or more, or there are not 1 to 6 passengers in total: RangeError;
  • seat is not a string: TypeError; a string that is not 'window', 'aisle' or 'any': RangeError;
  • children without any adult: the ValidationError given in the file, with field set to 'adults'.

Give every error a message that names the bad value. The tests check the classes and field only, never the messages, so word them as you like.

Starter code · booking.mjs

/** A broken booking rule. It is given: throw it, do not change it. */
export class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

export const SEATS = ['window', 'aisle', 'any'];
export const MAX_PASSENGERS = 6;

/**
 * Checks a bus booking request and returns it with the passenger count added.
 * @param {{ adults: number, children: number, seat: string }} input
 * @returns {{ adults: number, children: number, seat: string, passengers: number }}
 */
export function validateBooking(input) {
  if (!input) throw new Error('no booking');
  if (input.adults + input.children > MAX_PASSENGERS) throw new Error('too many passengers');
  if (!SEATS.includes(input.seat)) throw new Error('unknown seat');
  return { ...input, passengers: input.adults + input.children };
}
The sample tests · booking.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { validateBooking, ValidationError } from './booking.mjs';

/** The error a call throws (the test fails when it throws nothing). */
function thrownBy(call) {
  try {
    call();
  } catch (error) {
    return error;
  }
  assert.ok(false, 'expected the call to throw');
}

test('a good request comes back with the passenger count', () => {
  assert.deepEqual(validateBooking({ adults: 2, children: 1, seat: 'window' }), { adults: 2, children: 1, seat: 'window', passengers: 3 });
});

test('a request that is not an object is a TypeError', () => {
  assert.ok(thrownBy(() => validateBooking(null)) instanceof TypeError);
  assert.ok(thrownBy(() => validateBooking('2 adults')) instanceof TypeError);
});

test('counts that are not numbers are a TypeError', () => {
  assert.ok(thrownBy(() => validateBooking({ adults: '2', children: 0, seat: 'any' })) instanceof TypeError);
});

test('counts that are numbers but not allowed are a RangeError', () => {
  assert.ok(thrownBy(() => validateBooking({ adults: 1.5, children: 0, seat: 'any' })) instanceof RangeError, '1.5 adults');
  assert.ok(thrownBy(() => validateBooking({ adults: 2, children: -1, seat: 'any' })) instanceof RangeError, '-1 children');
  assert.ok(thrownBy(() => validateBooking({ adults: 4, children: 3, seat: 'any' })) instanceof RangeError, '7 passengers');
});

test('a seat that is not one of the choices is a RangeError, a seat that is not text a TypeError', () => {
  assert.ok(thrownBy(() => validateBooking({ adults: 1, children: 0, seat: 'front' })) instanceof RangeError);
  assert.ok(thrownBy(() => validateBooking({ adults: 1, children: 0, seat: 3 })) instanceof TypeError);
});

test('children without an adult break a booking rule: a ValidationError for the adults field', () => {
  const error = thrownBy(() => validateBooking({ adults: 0, children: 2, seat: 'aisle' }));
  assert.ok(error instanceof ValidationError, `expected a ValidationError, got ${error}`);
  assert.equal(error.field, 'adults');
});
A hint

Check in the order the list gives: first the kind of value (typeof, and null, which typeof calls 'object'), then whether a number is allowed (Number.isInteger, the range), and the booking rule last. A small helper such as checkCount(value, field) saves writing the two count checks twice.

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 choose-type.mjs calls setQuantity with 2, 0, 2.5, 25 and '3'. What does it print? Decide before you look at the recorded output.

    What does this program print? Choose one answer.

    // Two kinds of mistake, two classes: the wrong kind of value is a TypeError, a value outside the allowed ones is a
    // RangeError. The caller treats them differently.
    const MAX_PER_ORDER = 10;
    
    function setQuantity(cart, sku, qty) {
      if (typeof qty !== 'number') throw new TypeError(`qty must be a number, got ${typeof qty}`);
      if (!Number.isInteger(qty) || qty < 1 || qty > MAX_PER_ORDER) {
        throw new RangeError(`qty must be a whole number from 1 to ${MAX_PER_ORDER}, got ${qty}`);
      }
      cart.set(sku, qty);
    }
    
    const cart = new Map();
    for (const qty of [2, 0, 2.5, 25, '3']) {
      try {
        setQuantity(cart, 'TEA-250', qty);
        console.log(`${JSON.stringify(qty).padEnd(5)} -> cart has ${cart.get('TEA-250')}`);
      } catch (error) {
        if (error instanceof RangeError) {
          console.log(`${JSON.stringify(qty).padEnd(5)} -> tell the shopper: ${error.message}`);
        } else {
          console.log(`${JSON.stringify(qty).padEnd(5)} -> a bug in the calling code: ${error.name}`);
        }
      }
    }
    Show the answer to question 1

    Answer: it prints

    2     -> cart has 2
    0     -> tell the shopper: qty must be a whole number from 1 to 10, got 0
    2.5   -> tell the shopper: qty must be a whole number from 1 to 10, got 2.5
    25    -> tell the shopper: qty must be a whole number from 1 to 10, got 25
    "3"   -> a bug in the calling code: TypeError

    0, 2.5 and 25 are numbers, the right kind of value, but not allowed ones, so each is a RangeError and the caller shows its message. '3' is text, the wrong kind of value, so it is a TypeError, which the caller treats as a bug in the code that called setQuantity.

  2. Question 2 of 7 Which error does null.length throw?

    Choose one answer.

    Show the answer to question 2

    Answer: TypeError

    Reading a property needs an object, and null cannot be turned into one, so the operation cannot work on that kind of value: a TypeError. A ReferenceError is about names that cannot be resolved, and null is a value, not a name.

  3. Question 3 of 7 setStatus(order, status) accepts 'paid', 'shipped' or 'returned' and receives the string 'shiped'. Which built-in class fits the error it throws?

    Choose one answer.

    Show the answer to question 3

    Answer: RangeError, because a string is the right kind of value but this one is not in the allowed set

    ECMA-262 defines RangeError for a value that is not in the set or range of allowed values. The argument is a string, as expected, so the kind is right; only the value is not accepted. TypeError would fit a number or null passed as the status.

  4. Question 4 of 7 document.querySelector('[') throws an error whose name is 'SyntaxError', yet error instanceof SyntaxError is false. Why?

    Choose one answer.

    Show the answer to question 4

    Answer: It is a DOMException named SyntaxError, which is a different class from JavaScript's SyntaxError

    Web APIs report problems with DOMException objects identified by name, and the Web IDL standard reuses the name 'SyntaxError' for text that a web API cannot parse. Check error instanceof DOMException && error.name === 'SyntaxError' for it, and error instanceof SyntaxError for JavaScript's own parse errors.

  5. Question 5 of 7 Which of these checks keep working when Node.js is upgraded or the code runs in another engine?

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • error.code === 'ENOENT' for a file that does not exist in Node.js
    • error.name === 'DataCloneError' for a DOMException from structuredClone
    • error instanceof TypeError

    The class, the name of a DOMException and a documented code are part of a contract. A message is each engine's own wording, and the lesson's table shows V8 changing the message of the same JSON.parse error twice between Node.js 18 and Node.js 22.

  6. Question 6 of 7 What does Promise.any([a, b, c]) reject with when all three promises reject?

    Choose one answer.

    Show the answer to question 6

    Answer: An AggregateError whose errors property lists the three reasons

    Promise.any is the one place where the standard library creates an AggregateError: it collects every rejection reason, in the order of the input, in the errors list.

  7. Question 7 of 7 Code in an ES module assigns to a property of a frozen object. What happens?

    Choose one answer.

    Show the answer to question 7

    Answer: It throws a TypeError, because modules are strict code

    In strict code, which every module is, writing to a read-only property throws a TypeError, as error-zoo.mjs shows. A classic script without 'use strict' skips the same assignment without any error, which is one reason to prefer strict code.

Interview questions

Fresher · What is the difference between TypeError and RangeError, and when should your own code throw each?

A TypeError means an operation could not work on the kind of value it got: calling something that is not a function, reading a property of null, or, in your own code, receiving text where a number belongs. A RangeError means the value was the right kind but not an allowed one: an array length of −1, 101 decimals for toFixed, a quantity of 0, or a status string that is not in the accepted set. In your own functions, check the kind first and throw a TypeError when it is wrong, then check the value and throw a RangeError when it is outside what you accept. The difference helps the caller: a TypeError usually points at a bug in the calling code, while a RangeError often describes a choice a person can correct.

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

  1. Mid to senior

    You maintain a payments library that 40 services use. A teammate wants to change the TypeError it throws for an amount given as text into a RangeError, to make the library consistent. What do you weigh before agreeing, and how would you ship the change?

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

  2. Fresher to mid

    A search box passes what users type to document.querySelector, and the code handles failures with if (error instanceof SyntaxError) showHint(). Users who type "[" get no hint, and the error tracker reports a crash. Why, and how would you fix the handler?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The engine throws TypeError, RangeError, ReferenceError, SyntaxError, URIError and AggregateError; EvalError remains only for old code, and engine limits such as deep recursion vary between engines.
  • TypeError covers the wrong kind of value; RangeError covers a value of the right kind outside the allowed set or range, an unknown status string included.
  • Throw the same classes from your own functions, check the kind before the value, and give the rules of your application error classes of their own.
  • AggregateError reports several failures at once in its errors list; SuppressedError comes with using.
  • Messages differ between engines and between releases of one engine; test the class, the name or a documented code.
  • Node.js identifies system errors and its own errors by code; web APIs throw DOMExceptions identified by name, and a DOMException named SyntaxError is not a JavaScript SyntaxError.

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.