JavaScript Error Types: Complete Guide
JavaScript has seven core error types beyond the base Error. Learn when each occurs, what their names and messages look like, and how to handle them correctly with try...catch.
JavaScript has seven core built-in error types. Each one is thrown in specific situations by the engine, and each can be caught and checked with instanceof in a try...catch block. Knowing which error type you are dealing with helps you write better messages, filter errors in a catch block, and understand what went wrong from a stack trace.
All error types inherit from Error, so you can check the type of any caught error through its name property or with the instanceof operator.
| Error type | Trigger condition |
|---|---|
| Error | Generic base error |
| TypeError | Wrong type for an operation |
| ReferenceError | Using an undeclared variable |
| RangeError | Value outside allowed range |
| SyntaxError | Invalid syntax in parsed code |
| URIError | Malformed URI parameter |
| AggregateError | Multiple errors wrapped into one |
TypeError
TypeError is the most common runtime error. It means you tried to do something with a value that is not valid for its type: calling a non-function, accessing a property on null or undefined, or using new on something that is not a constructor.
const x = null;
try {
x.property;
} catch (e) {
console.log(e.name); // "TypeError"
console.log(e instanceof TypeError); // true
}Reading a property off null has no meaningful result, so the engine throws instead of returning undefined. Calling a non-function value the same way you would call a function produces the same error type:
try {
"hello"();
} catch (e) {
console.log(e.message); // "\"hello\" is not a function"
}In your error handling, TypeError often means a value is unexpectedly null or undefined. This is one of the most common bug categories in production JavaScript, and it is usually worth logging the variable name along with the error.
ReferenceError
ReferenceError is thrown when you try to use a variable that has not been declared anywhere in scope. The engine looks up the name in the current scope chain, fails to find it, and throws.
try {
console.log(nonExistentVariable);
} catch (e) {
console.log(e.name); // "ReferenceError"
console.log(e.message); // "nonExistentVariable is not defined"
}ReferenceError also happens when you access a let or const variable before its declaration runs, a period called the temporal dead zone:
try {
console.log(city); // ReferenceError, not yet initialized
let city = "Austin";
} catch (e) {
console.log(e instanceof ReferenceError); // true
}The key difference from TypeError is that ReferenceError means the variable was never declared, while TypeError means the variable exists but was used the wrong way. Writing let n; n.length throws a TypeError, not a ReferenceError, because n was declared, just not given a usable value.
RangeError
RangeError fires when a numeric value falls outside the allowed range for an operation. Common cases include invalid array lengths and numbers too large for toFixed or toPrecision.
try {
new Array(-1); // Negative array length
} catch (e) {
console.log(e.message); // "Invalid array length"
}
try {
(1.23).toFixed(101); // toFixed only accepts 0 to 100
} catch (e) {
console.log(e instanceof RangeError); // true
}RangeError also fires when the call stack overflows from unbounded recursion, which is a different kind of range problem: the recursion depth itself exceeds what the engine allows.
function recurse() {
recurse();
}
try {
recurse();
} catch (e) {
console.log(e.message); // "Maximum call stack size exceeded"
}SyntaxError
The parser throws SyntaxError when code contains invalid syntax. A syntax error in the static script itself cannot be caught at runtime, because the parser rejects the whole script before any code executes. SyntaxError can only be caught when it comes from dynamic code evaluation, such as JSON.parse or eval:
try {
JSON.parse("{ bad json }");
} catch (e) {
console.log(e instanceof SyntaxError); // true
}eval and the Function constructor throw the same way when the string of code they receive fails to parse as valid JavaScript, since both compile their argument at the moment they run:
try {
eval("1 + 2 +");
} catch (e) {
console.log(e instanceof SyntaxError); // true
}SyntaxError at runtime is almost always from bad JSON. Always wrap JSON.parse in try...catch whenever the input might be malformed, such as data coming from a network response or user-supplied text.
URIError
URIError is thrown when encodeURI, encodeURIComponent, decodeURI, or decodeURIComponent receive a malformed URI:
try {
decodeURIComponent("%"); // Incomplete percent encoding
} catch (e) {
console.log(e instanceof URIError); // true
}URIError is rare in practice, because most URL parsing in modern JavaScript uses the URL constructor, which throws a TypeError for an invalid URL instead. You will only encounter URIError if you directly call the encodeURI or decodeURI family of functions.
AggregateError
Introduced in ES2021, AggregateError wraps multiple errors into a single error object. Its errors property holds an array of every individual error that contributed to it.
try {
await Promise.any([
Promise.reject(new Error("network down")),
Promise.reject(new Error("timeout")),
]);
} catch (e) {
console.log(e instanceof AggregateError); // true
console.log(e.errors.length); // 2
}Promise.any rejects with an AggregateError only when every promise in the iterable rejects. You can also construct one yourself to report several validation failures at once instead of stopping at the first problem found:
const failures = [
new Error("File A failed to upload"),
new Error("File B is too large"),
];
throw new AggregateError(failures, "Upload completed with errors");The catch binding and destructuring
You can destructure the caught error directly in the catch clause to pull out just the fields you need:
try {
throw new TypeError("Expected a string, got number");
} catch ({ name, message }) {
console.log(`[${name}] ${message}`); // "[TypeError] Expected a string, got number"
}This works because catch accepts any binding pattern, the same way a function parameter does, so destructuring name and message skips having to write error.name and error.message separately.
Handling multiple error types
A well-written catch block checks the error type before deciding what to do, instead of treating every failure the same way:
async function loadData(url) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
return { error: "Network error. Check your connection." };
}
throw error;
}
}A TypeError here means fetch itself failed, usually from a dropped connection, so the function returns a friendly message instead of crashing. Any other error type, such as a SyntaxError from a response body that failed to parse, gets rethrown so it stays visible instead of being silently treated as a network problem.
Each branch in the diagram handles one known error category. Unknown errors fall through to the final branch, which logs and rethrows so they stay visible for debugging instead of being silently swallowed.
Creating your own error types
When none of the built-in types capture the meaning of your error, extend Error to create a named type of your own:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}The field property here is something none of the seven built-in types can carry, which is exactly the case where a custom error earns its keep:
try {
throw new ValidationError("Email format is invalid", "email");
} catch (error) {
if (error instanceof ValidationError) {
highlightField(error.field);
}
}
``` For a full guide on custom errors, see [creating custom errors in JavaScript](/javascript/creating-custom-errors-in-js-complete-tutorial). For the fundamentals of catching errors, see the [try...catch guide](/javascript/js-try-catch-tutorial-advanced-error-handling).Rune AI
Key Insights
- JavaScript has 7 core error types: Error, TypeError, ReferenceError, RangeError, SyntaxError, URIError, and AggregateError.
- TypeError is the most common runtime error, triggered by operations on values of the wrong type.
- ReferenceError means a variable was used without being declared.
- SyntaxError at runtime only comes from dynamic code like JSON.parse(), not from static script parsing.
- AggregateError groups multiple errors, useful for Promise.any() and batch operations.
- Use instanceof in catch blocks to handle each error type appropriately.
Frequently Asked Questions
What is the difference between TypeError and ReferenceError?
Can I catch SyntaxError at runtime?
What is AggregateError used for?
Conclusion
JavaScript has a structured error system, not just one generic Error type. Understanding which error type your code might throw lets you write precise catch blocks with instanceof checks, display more helpful messages to users, and avoid silently swallowing errors you should not handle. When none of the built-in types fit your use case, extend Error and create your own.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.