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 type | Thrown when | Typical trigger |
|---|---|---|
TypeError | A value is not of the expected type | Calling a non-function, reading a property of null/undefined |
ReferenceError | A variable name can’t be resolved | Using a variable that was never declared |
RangeError | A value is outside its allowed range | Invalid array length, toFixed(200), deep recursion |
SyntaxError | Code is grammatically invalid | JSON.parse of bad text, eval of broken code |
URIError | A URI function gets malformed input | decodeURIComponent('%') |
EvalError | Legacy; rarely thrown by modern engines | Kept for backward compatibility |
AggregateError | Several errors are wrapped into one | Promise.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 Errorlast (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 —
TypeErrorfor bad arguments,RangeErrorfor out-of-bounds values — so callers can branch meaningfully. - Use
instanceofrather than comparingerr.namestrings; it survives minification and respects subclassing. - Always re-throw errors you don’t explicitly handle instead of silently swallowing them.
- Order
instanceofchecks from most specific to most general, since every type also matchesError. - Remember that most
SyntaxErrors are thrown at parse time and cannot be caught — onlyJSON.parseandevalproduce catchable ones. - Don’t rely on
EvalError; it’s effectively legacy and modern engines rarely throw it.