Skip to content
JavaScript js error-handling 4 min read

Built-in Error Types

When something goes wrong at runtime, JavaScript doesn’t throw a generic blob — it throws a specific kind of error that tells you what category of mistake occurred. A TypeError means you used a value the wrong way; a ReferenceError means a name doesn’t exist; a RangeError means a value is out of bounds. Recognizing these built-in types lets you read stack traces faster, branch on instanceof to recover from only the failures you understand, and re-throw the rest. They all share one common ancestor: the base Error class.

The Error class and its subclasses

Every built-in error is a subclass of Error, so they all carry the same three core properties: name (the type, e.g. "TypeError"), message (the human-readable description), and stack (where it was thrown). Because they share an ancestor, err instanceof Error is true for any of them, while err instanceof TypeError narrows to one specific kind.

const err = new RangeError("value too large");
console.log(err.name);              // "RangeError"
console.log(err.message);           // "value too large"
console.log(err instanceof RangeError); // true
console.log(err instanceof Error);      // true

The standard error types

JavaScript defines a fixed set of native error constructors. The table below shows when each is thrown by the engine — you can also throw any of them yourself.

Error typeThrown whenTypical trigger
TypeErrorA value is not of the expected typeCalling a non-function, reading a property of null/undefined
ReferenceErrorA variable name can’t be resolvedUsing a variable that was never declared
RangeErrorA value is outside its allowed rangeInvalid array length, toFixed(200), deep recursion
SyntaxErrorCode is grammatically invalidJSON.parse of bad text, eval of broken code
URIErrorA URI function gets malformed inputdecodeURIComponent('%')
EvalErrorLegacy; rarely thrown by modern enginesKept for backward compatibility
AggregateErrorSeveral errors are wrapped into onePromise.any when every promise rejects

TypeError

The most common runtime error. It signals that an operation was performed on a value of the wrong type — most often reading or calling something on null or undefined.

const user = null;
console.log(user.name); // TypeError: Cannot read properties of null (reading 'name')

const notAFn = 42;
notAFn(); // TypeError: notAFn is not a function

ReferenceError

Raised when you reference an identifier that has never been declared in any reachable scope. This is distinct from a variable that exists but holds undefined.

console.log(total); // ReferenceError: total is not defined

let count;
console.log(count); // undefined — no error, the binding exists

RangeError

Thrown when a numeric or structural value is technically the right type but outside the permitted range.

const arr = new Array(-1);     // RangeError: Invalid array length
(255).toString(40);            // RangeError: toString() radix must be 2-36

function recurse() { return recurse(); }
recurse();                     // RangeError: Maximum call stack size exceeded

SyntaxError

Most syntax errors fire at parse time, before your script runs, so a try/catch can’t catch them. But the ones thrown by JSON.parse and eval happen at runtime and are catchable — which is exactly why you wrap JSON.parse in try/catch.

try {
  JSON.parse("{ bad json }");
} catch (err) {
  console.log(err instanceof SyntaxError); // true
  console.log(err.name);                   // "SyntaxError"
}

AggregateError

Introduced in ES2021, AggregateError bundles multiple errors into one. The wrapped errors live on its .errors array. Promise.any produces one when every input promise rejects.

try {
  await Promise.any([
    Promise.reject(new Error("server A down")),
    Promise.reject(new Error("server B down")),
  ]);
} catch (err) {
  console.log(err instanceof AggregateError); // true
  console.log(err.errors.map((e) => e.message));
}

Output:

true
[ 'server A down', 'server B down' ]

Branching on error type with instanceof

A single catch block receives every kind of error, so the standard pattern is to inspect the caught value with instanceof and handle each type differently. Crucially, you should re-throw anything you don’t recognize — swallowing an unexpected error hides real bugs.

function loadSettings(raw) {
  try {
    const data = JSON.parse(raw);
    return data.theme.toUpperCase();
  } catch (err) {
    if (err instanceof SyntaxError) {
      return "DEFAULT"; // malformed JSON — fall back
    }
    if (err instanceof TypeError) {
      return "NO_THEME"; // data.theme was missing
    }
    throw err; // unknown failure — let it propagate
  }
}

console.log(loadSettings('{"theme":"dark"}')); // "DARK"
console.log(loadSettings("not json"));          // "DEFAULT"
console.log(loadSettings("{}"));                // "NO_THEME"

Check for instanceof Error last (or use it as a fallback), since every built-in type also matches it. Ordering specific types before the base class keeps each branch reachable.

Because instanceof walks the prototype chain, it works across subclasses too. If you define a custom error that extends Error, the same branching style applies — see Custom errors.

Best Practices

  • Throw the most specific built-in type that fits — TypeError for bad arguments, RangeError for out-of-bounds values — so callers can branch meaningfully.
  • Use instanceof rather than comparing err.name strings; it survives minification and respects subclassing.
  • Always re-throw errors you don’t explicitly handle instead of silently swallowing them.
  • Order instanceof checks from most specific to most general, since every type also matches Error.
  • Remember that most SyntaxErrors are thrown at parse time and cannot be caught — only JSON.parse and eval produce catchable ones.
  • Don’t rely on EvalError; it’s effectively legacy and modern engines rarely throw it.
Last updated June 1, 2026
Was this helpful?