skip to content

Which built-in error constructors does JavaScript itself throw at runtime — TypeError, ReferenceError, RangeError, SyntaxError, URIError — and what kind of failure does each one signal?

level: juniorimportance: must knowfreq 72%

answer

  1. one base constructor, several specialised ones
  2. wrong type versus wrong value
  3. unresolvable name gets its own error
  4. parse errors arrive before code runs
  5. malformed percent-escapes have a dedicated type

basics

~20 s

JavaScript throws TypeError when a value is the wrong kind of thing for the operation, ReferenceError when an identifier does not resolve, RangeError when a value of the right type is out of range, SyntaxError when source cannot be parsed, and URIError for malformed URI escapes.

solid answer

~50 s

All of them derive from `Error`, so `err instanceof Error` is true for every one, and each has its own `name` on its prototype. `TypeError` means the value is the wrong kind of thing for the operation — reading a property of `null`, calling a non-function, assigning to a `const` binding. `ReferenceError` means an identifier could not be resolved at all. `RangeError` means the type was fine but the value was not acceptable — `new Array(-1)`, `"x".repeat(-1)`, `(1).toFixed(101)`. `SyntaxError` comes from the parser, so you normally only catch it when parsing happens at runtime: `JSON.parse`, `new Function`, `eval`. `URIError` comes from the URI encode/decode functions on malformed input. `AggregateError` (ES2021) is the odd one out: it wraps a list of failures in an `errors` property. `EvalError` still exists but nothing in the modern spec throws it.

code

javascript · 20 lines
javascript
const cases = [
  () => null.foo,
  () => notDeclaredAnywhere,
  () => new Array(-1),
  () => JSON.parse('{'),
  () => decodeURIComponent('%'),
];

for (const run of cases) {
  try {
    run();
  } catch (err) {
    console.log(err.name, '|', err instanceof Error);
  }
}
// TypeError | true
// ReferenceError | true
// RangeError | true
// SyntaxError | true
// URIError | true

go deeper

for a junior

Be able to name TypeError, ReferenceError, RangeError, SyntaxError and URIError and give one concrete trigger for each, and say that all of them are instances of Error.

for a middle

Explain where name and message actually live on the object, why a syntax error in the current file cannot be caught locally, and the TypeError-versus-ReferenceError distinction between a declared-but-undefined value and an unresolvable name.

for a senior

Show judgment about which built-in to throw from your own code — TypeError for wrong argument kinds, RangeError for out-of-domain values — and point out which real failures are silent instead of thrown, because those are what turn into distant bugs.

for a principal

Own the convention across a codebase: when built-ins are the right signal versus when a domain type is, and how a consistent choice makes catch sites and telemetry groupable instead of a wall of undifferentiated Error entries.

## One base, several specialisations Every error the language throws is an object whose prototype chain ends at `Error.prototype`. The specialised constructors — `EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError` and, since ES2021, `AggregateError` — each own a prototype object whose own prototype is `Error.prototype`. They are siblings of each other, not a deep hierarchy. Two consequences follow: `err instanceof Error` is true for all of them, and the useful test is against the specific constructor. Each specialised prototype carries a `name` string, and `message` is an own property set from the constructor argument: ```js const e = new TypeError('nope'); e.name; // "TypeError" (lives on TypeError.prototype) e.message; // "nope" (own property of e) String(e); // "TypeError: nope" e instanceof TypeError; // true e instanceof Error; // true ``` `Error.prototype.toString()` is essentially `name + ": " + message` (it degrades gracefully when either is empty), which is why interpolating an error into a template literal gives you the type and the text and nothing more. ## TypeError — the value is the wrong kind of thing This is by far the most common one in day-to-day work. It fires when an operation is applied to an operand it cannot accept: ```js null.foo; // TypeError: Cannot read properties of null const n = 5; n(); // TypeError: n is not a function const c = 1; c = 2; // TypeError: Assignment to constant variable [].reduce((a, b) => a + b); // TypeError: Reduce of empty array with no initial value ``` It is also what the language throws for a symbol used in arithmetic, and — in strict mode — for writing to a non-writable property. When you validate your own function's arguments, `TypeError` is the conventional thing to throw for "you passed the wrong kind of value". ## ReferenceError — the name does not resolve Reading an identifier that is not bound anywhere throws `ReferenceError`. In strict mode, *assigning* to an undeclared identifier throws it too, where sloppy mode would silently create a global. ```js console.log(notDeclaredAnywhere); // ReferenceError: notDeclaredAnywhere is not defined ``` The classic confusion is with `TypeError`: `foo.bar` where `foo` is undeclared is a `ReferenceError` (the name failed), while `foo.bar` where `foo` is declared but holds `undefined` is a `TypeError` (the value failed). ## RangeError — right type, unacceptable value The argument is of the expected type but outside the permitted domain: ```js new Array(-1); // RangeError: Invalid array length 'x'.repeat(-1); // RangeError: Invalid count value (1.5).toFixed(101); // RangeError: toFixed() digits argument must be between 0 and 100 ``` Engines also surface stack overflow this way — V8 throws `RangeError: Maximum call stack size exceeded` for runaway recursion — but that particular mapping is an engine choice, not something the specification mandates. ## SyntaxError — the source cannot be parsed A syntax error in a script or module is thrown while that unit is being parsed, before any of its statements run, so a `try` block inside the same file cannot catch it. You only catch `SyntaxError` when parsing happens at runtime as a deliberate operation: ```js try { JSON.parse('{'); } catch (err) { err instanceof SyntaxError; // true } ``` `new Function(...)` and `eval(...)` behave the same way, and a dynamic `import()` of a syntactically broken module rejects with a `SyntaxError`. ## URIError — malformed percent-escapes Thrown only by the four global URI functions when their input is not well formed: `decodeURIComponent('%')` and `encodeURI('\uD800')` (a lone surrogate) both throw `URIError: URI malformed`. ## AggregateError — several failures at once Added in ES2021, it is the only built-in error that carries a collection: `new AggregateError([e1, e2], 'all failed')` exposes the individual failures on an `errors` property alongside the usual `message`. It is what `Promise.any` produces when every input rejects, and you can construct it yourself when reporting a batch of failures. ## EvalError — a fossil The constructor exists for backward compatibility, but nothing in the modern specification throws it. Recognise it, do not use it. ## What does *not* throw JavaScript's older design decisions mean many failures produce a value rather than an error: reading a missing property yields `undefined`, arithmetic on non-numbers yields `NaN`, and out-of-range array indexes yield `undefined` rather than a range error. Knowing which failures are silent is as valuable as knowing the error types, because the silent ones are the bugs that travel furthest from their cause. ## Using this in a `catch` Since these constructors are siblings, order of checks does not matter, but specificity does: catching and branching on `err instanceof SyntaxError` around a parse call is meaningful, whereas a bare catch that swallows everything hides `TypeError`s that are your own bugs.

  • When can you actually catch a SyntaxError?
    Only when parsing happens at runtime as an operation you invoked: `JSON.parse`, `new Function`, `eval`, or a dynamic `import()` of a broken module, which rejects with one. A syntax error in the script or module you are currently running is thrown while that unit is parsed, before any statement in it executes, so no `try` block inside the same file is even in existence yet to catch it.
  • What does AggregateError add over a plain Error?
    An `errors` property holding the individual failures, so one thrown object can report a batch. You construct it as `new AggregateError(iterableOfErrors, message)`; the `message` describes the batch and `errors` carries the details. It is what `Promise.any` rejects with when every input rejects. Without it you would have to invent an ad-hoc field to attach the list.
  • Is EvalError still thrown by anything?
    No. The constructor is retained for backward compatibility so old code that references it keeps working, but nothing in the current specification throws it, and engines do not produce it. Recognising the name is enough — there is no reason to throw it in new code, and a candidate who lists it as a live error type is reciting an outdated table.

saying these in an interview costs you the question

  • Says TypeError and ReferenceError are two names for the same failure
  • Claims every runtime failure in JavaScript throws an error
  • Thinks try/catch can catch a syntax error in the same file
  • Believes reading a missing property throws instead of yielding undefined
  • Says RangeError only comes from array operations

context