Error types, cause and custom errors: errors that tell you what went wrong
The built-in error types, wrapping errors with cause, writing custom error classes that work with instanceof, AggregateError, Error.isError, and why you never throw strings.
Comes up in: JavaScript, Debugging
Learn#
Irfan’s error tracker shows 4,000 copies of “Something went wrong” and nothing else. Every layer of his app caught errors and threw a new message, so the real cause was lost.
JavaScript has built-in error types: TypeError (wrong kind of value), RangeError (number out of range), SyntaxError (bad code or bad JSON), ReferenceError (unknown variable), and a few more.
When you add context, keep the original as the error cause:
class PaymentError extends Error {
constructor(message, options) {
super(message, options);
this.name = "PaymentError";
}
}
function charge() {
try {
JSON.parse("{ bad json");
} catch (err) {
throw new PaymentError("Could not read the bank's reply", { cause: err });
}
}
try { charge(); } catch (e) {
console.log(e instanceof PaymentError, e instanceof Error, e.name);
console.log(e.message, "← because:", e.cause.name);
}Output (verified)
true true PaymentError Could not read the bank's reply ← because: SyntaxError
PaymentError is a custom error: a class that extends Error, so callers can test for it with instanceof and handle it on its own.
Call stack
- charge
- JSON.parse
State
- err: SyntaxError
Console
empty
JSON.parse fails deep inside charge() and throws a SyntaxError.
Read the steps as text
- JSON.parse fails deep inside charge() and throws a SyntaxError.
- charge() catches it and throws a PaymentError with a clear message. The SyntaxError goes in cause.
- The caller checks instanceof PaymentError and shows a payment message. The log keeps both errors and both stacks.
Predict#
Predict first
Code runs throw 'card declined'. What does the catch block's e.stack give?
A string is not an Error, so it has no stack and no name. You lose the place where it happened. Throwing a string looks harmless because the message still arrives.
Interview angle#
Say it like this — about 30 seconds
“I always throw Error objects, never strings, so I keep a stack trace. When a lower layer fails, I wrap it in an error that explains what the user was doing, and pass the original as cause. For errors callers must handle differently, I write a class that extends Error, calls super with the message and options, and sets name. Then callers use instanceof. AggregateError holds several errors at once, for example from Promise.any.”
Follow-up How do you create a custom error class correctly?
super(message, options) to keep cause, set this.name, and add fields such as status.Follow-up Why set this.name?
Follow-up When is instanceof Error not enough?
Error.isError(value) checks for a real error object.Practice#
Guided
Write an HttpError class, then wrap a failed request in a clear error that keeps its cause.
Done when
new HttpError(404, "Not found")has the right name, status and message.- It passes
instanceof HttpErrorandinstanceof Error, and has a stack. loadProfilerejects with a new message and the original error ascause.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
How do you attach the original error when throwing a new one?
new Error(message, { cause: originalError })
Why never throw a string?
Strings have no stack trace and no name.
What three things does a good custom error class do?
Extends Error, calls super(message, options), sets this.name.
What does AggregateError hold?
Several errors at once, in its errors array.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
TypeError is the most common: reading a property of null, or calling something that is not a function. RangeError comes from things like new Array(-1) or a stack overflow. Check error.name or instanceof to tell them apart.
Check yourself: Which error does null.x throw?
TypeError.
How it works underneathSenior rounds
super(message, options) lets the Error constructor set message, stack and cause Source: MDN: Error: cause. Class syntax keeps the prototype chain right, so instanceof works; old ES5 transpile targets broke this.
Check yourself: Why pass options to super() in a custom error?
So the cause option is stored on the error.
Edge cases and trapsWhere people slip
AggregateError holds many errors in .errors; Promise.any rejects with one.Error.isError(value) is new in ES2026: Chrome 134, Firefox 138, Node.js 24.3.0; not yet in Safari.error.message.Check yourself: Is Error.isError available in every browser today?
No. Check support and feature-detect it.
In productionStaff-level answers
Show users a short, helpful message; send the full error, its cause chain and stack to your error tracker (JS.13.02). Most trackers show causes as linked errors Source: ECMA-262: InstallErrorCause.
Check yourself: What should an error shown to users not contain?
Internal details such as stack traces, queries or secrets.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- How do you create a custom error class correctly?