skip to content

Error Handling

Covers how JavaScript raises, catches and describes failures: what throw actually does, how try/catch/finally alters control flow, the built-in Error hierarchy and your own subclasses, and how cause and stack traces preserve context. Interviewers probe this to see whether you handle errors deliberately or just wrap everything in a swallowing try/catch.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

16

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

open as a page

In JavaScript, which values are legal after `throw`, and what concretely breaks in the catching code when a function throws the string 'Network unavailable' instead of `new Error('Network unavailable')`?

level: juniorimportance: must knowfreq 72%

basics

~20 s

JavaScript's throw accepts any value: strings, numbers, plain objects, null, even undefined. Only Error instances carry name, message and a stack, so a thrown string leaves the catch block with no diagnostics, err.message undefined, and err instanceof Error false.

open as a page

In JavaScript, when does a `finally` block actually run? Does it still execute if the `try` block returns a value, or if an error is thrown that no `catch` clause handles?

level: juniorimportance: must knowfreq 75%

basics

~20 s

A JavaScript finally block runs on every exit from its try statement - normal completion, an early return, a break or continue, a caught error, and an error still propagating outward - which is exactly why cleanup belongs there.

open as a page

In JavaScript, given `class ValidationError extends Error {}`, the expression `new ValidationError('bad email').name` evaluates to "Error" rather than "ValidationError". Why, and what does a correctly written custom error class set?

level: middleimportance: must knowfreq 62%

basics

~20 s

The name property is inherited from Error.prototype, whose value is the string Error; an empty subclass overrides nothing. A correct custom error calls super(message) and then assigns its own name, plus whatever structured fields callers need.

open as a page

Inside a JavaScript catch block you decide the failure is not yours to handle. Compare `throw err;`, `throw new Error(err.message);`, and logging the error and returning — what does each leave the outer caller with?

level: middleimportance: must knowfreq 56%

basics

~20 s

A bare throw err rethrows the identical object, so its type, custom properties and original stack all survive. throw new Error(err.message) starts a fresh error and discards them. Logging and returning is worst: the caller sees a successful call and a bogus value.

open as a page

What does the JavaScript function `function counter() { let n = 0; try { return n; } finally { n = 100; } }` return, and would the answer change if it returned an object that the finally block then mutated?

level: middleimportance: must knowfreq 58%

basics

~20 s

It returns 0. The return expression is evaluated and its value parked before finally runs, so reassigning the variable afterwards changes nothing. Returning an object is different: the caller sees the mutation, because both hold the same reference.

open as a page

In JavaScript, what is an Error object's `stack` property, when are its frames captured, and why is parsing it fragile?

level: juniorimportance: should knowfreq 58%

basics

~20 s

stack is a string of the frames leading to where the Error was constructed — not where it was thrown. Every major engine provides it, but ECMAScript never specified it, so the exact text differs per engine and parsing it is unreliable.

open as a page

In JavaScript, what does the options object in `new Error('load failed', { cause: err })` actually do, and how do you walk the resulting chain of causes?

level: middleimportance: should knowfreq 42%

basics

~20 s

The ES2022 options bag installs a non-enumerable own cause property on the new error holding the original failure, so wrapping keeps the underlying error instead of discarding it. You read the chain by following err.cause repeatedly until it is absent.

open as a page

Is `try { ... } finally { ... }` with no `catch` clause legal JavaScript, and what does that shape express that a full try/catch/finally does not?

level: middleimportance: should knowfreq 40%

basics

~20 s

Yes, it is legal: a try statement needs a catch clause, a finally clause, or both. try/finally with no catch says cleanup must happen but this scope is not the right place to handle the error, so the error runs the cleanup and then keeps propagating.

open as a page

A callback passed to `setTimeout` throws, and the stack trace contains no frames from the function that scheduled it. Why does that happen in JavaScript, and how do you preserve the calling context?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The scheduling function returned long before the callback ran, so its frames were already popped off the call stack and cannot appear in a trace captured later. Preserve context by constructing an error at schedule time and attaching it as the cause of whatever the callback throws.

open as a page

Production JavaScript errors reach your tracker with stacks like `at n (app.9f3.js:1:24817)`. Explain what produced that and how you recover the original function, file and line.

level: seniorimportance: should knowfreq 38%

basics

~20 s

Minification renamed the functions and collapsed the bundle onto few lines, so the runtime honestly reports positions in the shipped file. Recovering the original names and lines requires the source map produced by that exact build, applied after the fact.

open as a page

In JavaScript, a caller writes `err instanceof ValidationError` and gets false even though the code that threw called `new ValidationError(...)`. What can make that check fail, and how would you discriminate error types more robustly?

level: seniorimportance: should knowfreq 38%

basics

~20 s

instanceof compares against one specific class object, so it fails whenever the thrown error was built from a different copy of that class: the defining module loaded twice, a separate realm with its own intrinsics, a downlevelled build with a broken prototype chain, or an error rebuilt after serialization.

open as a page

A JavaScript library invokes user-supplied callbacks and third-party code, so its catch blocks can receive absolutely any value. How do you turn an unknown caught value into something safe to log and rethrow, without the error path itself throwing?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Normalize at the boundary: check instanceof Error, fall back to Object.prototype.toString branding for cross-realm errors, and otherwise construct an Error from a defensively stringified value. Stringification must sit inside its own try, because null-prototype objects and throwing toString methods make String() throw.

open as a page

In JavaScript, why is writing a `return` statement inside a `finally` block treated as a bug, and what happens to an error that was already propagating when the `finally` block itself throws?

level: seniorimportance: should knowfreq 45%

basics

~20 s

An abrupt completion inside a finally block replaces whatever the try statement was already doing. A return there discards a pending error entirely, and a throw there replaces the original error, so the real failure disappears and only the cleanup failure survives.

open as a page

What does V8's `Error.captureStackTrace(targetObject, constructorOpt)` do, and why is it commonly called inside a custom error class constructor?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Error.captureStackTrace is a V8-only API that installs a stack property on any object you hand it. Passing a constructor as the second argument omits that constructor and everything above it from the trace, so a custom error class's own frames do not clutter the top.

open as a page

In JavaScript, why is putting the thrown value on the line after `throw` a syntax error, and how do you throw from an expression position such as an arrow function's concise body or the right-hand side of `??`?

level: middleimportance: nice to knowfreq 27%

basics

~20 s

The grammar forbids a line terminator between throw and its expression, and there is no valid argument-less throw, so a newline is a hard syntax error rather than silent semicolon insertion. throw is a statement, not an expression, so expression positions need a helper function or an immediately-invoked arrow.

open as a page