Every program gets bad data eventually: a negative age, a missing field, a string where a number belonged. The question isn't how to prevent it — you can't — but what your code does when it happens. One option is to soldier on, returning NaN, undefined, or an empty string and hoping the caller notices. That hope is rarely rewarded; the bad value travels silently downstream and breaks something ten functions away, with no clue where it went wrong.
The other option is to throw: stop the current operation right now and announce, loudly and with a message, exactly what was wrong. throw hands control back to the nearest willing catch, or crashes if there isn't one — but either way the failure is visible and located, not a quiet poison in your data.
flowchart TD
T["throw new Error(msg)"] --> S["unwind the call stack"]
S --> Q{"enclosing try/catch?"}
Q -- Yes --> C["catch handles it"]
Q -- No --> X["program crashes"]
style C fill:#1e7f3d,color:#fff
style X fill:#b91c1c,color:#fff
Throw an Error, with a message
The statement is just the word throw followed by a value. Convention — and good sense — says that value should be an Error object, created with new Error("..."). The string you pass becomes its message, the human-readable description of what went wrong, and JavaScript automatically attaches a stack trace recording where it was thrown.
throw new Error("insufficient funds");
The moment throw runs, the current line is abandoned. JavaScript unwinds outward, function by function, looking for an enclosing try...catch. Find one, and that catch runs with your error in hand; find none, and the program halts with your message printed. Either way, nothing after the throw in the same block executes.
function withdraw(balance, amount) {
if (amount > balance) {
throw new Error("insufficient funds");
}
return balance - amount;
}
try {
console.log(withdraw(100, 30));
console.log(withdraw(100, 500));
} catch (e) {
console.log("declined: " + e.message);
}
Throw early, fail loud
The disciplined way to use throw is to guard a function's preconditions at the very top: the things that must be true for the rest of the body to make sense. A function that divides by its argument can refuse a zero immediately rather than returning Infinity. A function that looks up a record can refuse a missing id rather than returning undefined and letting the caller crash three lines later with a confusing 'cannot read property of undefined'.
This is called failing fast. A failure caught at the door comes with a clear message and a precise location; the same failure deferred propagates into garbage that breaks something unrelated. When in doubt, throw at the boundary — the first line that receives untrusted input.
flowchart TD E["Error<br/>the base class"] --> T["TypeError"] E --> R["RangeError"] E --> Y["SyntaxError"] E --> M["YourError extends Error"] style E fill:#3776ab,color:#fff style M fill:#1e7f3d,color:#fff
Use the right kind of error
Error is the generic base, but JavaScript ships more specific subclasses that name the nature of the mistake — and you should reach for them, because that name is exactly what a downstream catch will read. TypeError means a value is the wrong type (calling a method on undefined, adding a number to an object). RangeError means a value is the right type but out of the allowed range (a negative array length, an array index past the end). SyntaxError is what JSON.parse throws for malformed text.
Throwing new RangeError("index out of bounds") instead of a plain Error tells the next layer precisely what family of problem this is, so it can choose to catch only that kind.
function nth(items, i) {
if (!Array.isArray(items)) throw new TypeError("expected an array");
if (i < 0 || i >= items.length) throw new RangeError("index out of bounds");
return items[i];
}
console.log(nth(["a", "b", "c"], 1));
try {
nth(["a", "b", "c"], 9);
} catch (e) {
console.log(e.name + ": " + e.message);
}
Your own error classes
When the built-in names don't fit, you can create your own. A custom error class extends Error, passes the message up to the base with super(message), and sets its own this.name. Now callers can distinguish your error from any other with instanceof — far more reliable than matching on a message string, which a single typo can break.
class InvalidAgeError extends Error {
constructor(message) {
super(message);
this.name = "InvalidAgeError";
}
}
Because it inherits from Error, an InvalidAgeError is still an Error — so a generic catch sees it too. That extends chain is what lets you be specific without losing the safety net of a broad handler.
class InvalidAgeError extends Error {
constructor(message) {
super(message);
this.name = "InvalidAgeError";
}
}
function setAge(age) {
if (typeof age !== "number" || age < 0) {
throw new InvalidAgeError("age must be a non-negative number");
}
return age;
}
try {
setAge(-5);
} catch (e) {
console.log(e instanceof InvalidAgeError);
console.log(e instanceof Error);
console.log(e.name + ": " + e.message);
}
flowchart TD C["catch (e)"] --> Q["e instanceof RangeError?"] Q -- Yes --> H["handle the range case"] Q -- No --> RE["throw e<br/>let it propagate"] style H fill:#1e7f3d,color:#fff style RE fill:#b45309,color:#fff
Handle one kind, re-throw the rest
A JavaScript catch is type-agnostic: it grabs every thrown value, regardless of kind. So when you want to handle only a specific error and let everything else keep propagating, you filter inside the catch with instanceof. If the error is the kind you expected, handle it; otherwise, throw e to put it back in flight for an outer handler.
try {
risky();
} catch (e) {
if (e instanceof RangeError) {
console.log("handled a range issue");
} else {
throw e;
}
}
Re-throwing preserves the original error and its stack, so the failure stays honest all the way up the stack.
What does this print? half throws for one of these inputs — trace which calls actually run.
function half(n) {
if (n === 0) throw new Error("no zero");
return 10 / n;
}
try {
console.log(half(5));
console.log(half(0));
console.log(half(2));
} catch (e) {
console.log("caught: " + e.message);
}
half(5) runs cleanly and prints first.
half(0) throws, so the third call never runs — control jumps to the catch.
Write factorial(n) that returns n! (so factorial(5) is 120) for n >= 0, and throws new Error("negative") when n < 0.
function factorial(n) {
// throw new Error("negative") for n < 0; otherwise return n!
}
Guard the bad case first: if (n < 0) throw new Error("negative").
Then loop i from 2 to n inclusive, multiplying into a result that starts at 1.
function factorial(n) {
if (n < 0) throw new Error("negative");
let result = 1;
for (let i = 2; i <= n; i++) result *= i;
return result;
}
This findUser(id) throws a bare string instead of a real Error. That loses the stack trace and means a caller's err.message comes back undefined. Change the throw so that what is thrown is instanceof Error and carries .message === "no user".
function findUser(id) {
if (id < 0) throw "no user";
return "user-" + id;
}
A thrown string is not an Error — it has no .message and no stack.
Use throw new Error("no user") instead of throw "no user".
function findUser(id) {
if (id < 0) throw new Error("no user");
return "user-" + id;
}
The custom error class BadEmailError is written for you. Complete validateEmail(s): if s does not include an @, throw new BadEmailError("missing @"); otherwise return true.
class BadEmailError extends Error {
constructor(message) {
super(message);
this.name = "BadEmailError";
}
}
function validateEmail(s) {
// throw BadEmailError if s has no "@", otherwise return true
}
Use s.includes("@") to test for the @ character.
Throw new BadEmailError("missing @") when it is absent; otherwise return true.
class BadEmailError extends Error {
constructor(message) {
super(message);
this.name = "BadEmailError";
}
}
function validateEmail(s) {
if (!s.includes("@")) throw new BadEmailError("missing @");
return true;
}
A catch block grabs an error. How do you handle only RangeErrors and let every other error keep propagating?
JavaScript's catch is type-agnostic — it grabs every thrown value. To handle one kind and pass the rest along, test with instanceof inside the catch and re-throw (throw e) the cases you don't handle.
Recap
throw new Error("message")creates an error and unwinds the call until a catch handles it, or the program crashes.- Fail fast: guard preconditions at the top of a function rather than returning
NaNor garbage that breaks code far away. - Pick the right built-in subclass —
TypeErrorfor wrong types,RangeErrorfor out-of-range values. - A custom error class extends
Error, callssuper(message), and setsthis.name;instanceof Errorstill matches it. - Filter inside one catch with
instanceofand re-throw (throw e) the kinds you don't handle. - Always throw an Error, never a bare string — you'd lose the stack trace and the
.message.
Next you'll step back from writing code to fixing it — systematic debugging strategies for when something goes wrong and you have to find out what.