Errors Are Objects With a Type
When JavaScript throws, it doesn't just hand you a string — it hands you an object. That object has a type (its constructor) and a few standard properties: name, message, and stack.
err.name is a short label like "TypeError". err.message is the human-readable description. err instanceof TypeError tells you the specific class. Knowing the type matters: it tells you whether the problem is a typo, a bad value, or code that didn't even parse.
There are seven built-in error types. Three you'll see constantly, four you'll meet occasionally.
SyntaxError: The Code Didn't Parse
A SyntaxError means JavaScript couldn't even read your code. It's not really a runtime error — the engine fails during parsing, before a single line runs. You can't try/catch a SyntaxError in the same file, because the whole file is rejected.
function greet(name {
return "hi, " + name;
}
// SyntaxError: Unexpected token '{'
Missing bracket, stray comma, a return outside a function — anything that breaks the grammar throws this. The fix is always to correct the source. The one place you can catch a SyntaxError is when parsing at runtime, like JSON.parse:
JSON.parse takes a string at runtime, so its syntax errors are catchable. Errors in your own source files are not.
ReferenceError: That Name Doesn't Exist
A ReferenceError fires when you reference a variable that hasn't been declared in any scope the current code can see.
Ninety percent of the time, this is a typo (totl instead of total). The other ten percent is scope — you're trying to use something declared in a different function or module.
There's one subtler cause: the temporal dead zone. let and const declarations exist from the top of their block, but you can't access them before the line that declares them:
x is a real binding by the time console.log(x) runs, but it hasn't been initialised yet. Hence the reference error. The fix is to move the access below the declaration.
TypeError: The Value Was the Wrong Shape
A TypeError means a value exists, but it's not the kind of thing the operation expects. Calling something that isn't a function, reading a property of null or undefined, assigning to a const — all TypeErrors.
"Cannot read properties of null (reading 'name')" is probably the single most common error message in JavaScript. The fix is either to guarantee the value exists, or to guard the access with optional chaining: user?.name.
Other flavours of TypeError:
Calling a number, reassigning a const, calling a method that doesn't exist — the value was the wrong type for what you asked it to do.
RangeError: The Number Is Out of Bounds
RangeError fires when a number is technically valid but outside the allowed range for a specific operation.
The classic source is infinite recursion, which blows the call stack:
"Maximum call stack size exceeded" almost always means a function is calling itself without a base case, or two functions are calling each other in a loop.
URIError and EvalError: The Rare Ones
URIError comes from the URI handling functions (encodeURI, decodeURIComponent, and friends) when they're given malformed input:
EvalError is a relic. Modern JavaScript engines don't actually throw it for anything — it still exists as a constructor for backward compatibility. You can create one manually, but you won't see one in the wild.
The Inheritance Chain
All these types inherit from a base Error. That means err instanceof Error is true for any of them, which is handy in a generic catch:
A catch block catches everything — including non-error values if someone does throw "oops". That last branch matters. Always narrow with instanceof before treating a caught value as an error object.
Creating Errors On Purpose
You can throw any of the built-in types yourself when your code detects a problem. Pick the type that matches the failure:
Matching the type to the failure isn't just cosmetic — it lets callers write targeted catch logic instead of parsing error messages.
Custom Error Classes
When the built-in types don't fit, extend Error:
Two things to remember: call super(message) so the base Error gets set up properly, and set this.name so logs show the right label. Custom fields like field let callers react to specific failure modes without parsing strings.
Next: Console and DevTools
Knowing the error types is half the battle — the other half is reading a stack trace and poking at state while the program runs. The browser's devtools (and Node's debugger) turn "it threw and I don't know why" into a few seconds of inspection. That's next.
Frequently Asked Questions
What are the built-in error types in JavaScript?
JavaScript ships with seven: Error (the base), SyntaxError, ReferenceError, TypeError, RangeError, URIError, and EvalError. Each is a constructor that produces an error object with name, message, and stack properties. You'll meet SyntaxError, ReferenceError, and TypeError most often.
What's the difference between a SyntaxError and a TypeError?
A SyntaxError means the code isn't valid JavaScript — the engine can't even parse it. A TypeError means the code parsed fine but did something illegal at runtime, like calling a non-function or reading a property of null. Syntax errors stop the whole script; type errors only fire when the bad line runs.
When do I get a ReferenceError in JavaScript?
When you use a name that hasn't been declared, or access a let/const variable in its temporal dead zone (before the declaration runs). Typos are the most common cause: consoel.log(x) throws ReferenceError: consoel is not defined. Check spelling and scope first.
Can I create my own error types?
Yes. Extend the built-in Error class: class ValidationError extends Error { }. Set this.name in the constructor so logs and catch branches can distinguish it. Custom error classes pay off when different failures need different handling.