Topic 13.1
Error Types
In one line
When JavaScript can't do what you asked, it creates an error object with a name, a message and a stack trace; the name (SyntaxError, ReferenceError, TypeError, RangeError and a few more) tells you what kind of mistake it is, and the stack trace tells you where.
Think of it like this
A doctor's report. When you feel ill, the doctor doesn't just say "something is wrong". The report names the kind of problem (a cold, a broken arm), describes it ("fracture of the left wrist"), and records how it happened ("fell off a bike, landed on the hand"). A JavaScript error is that report: the name is the kind of problem, the message describes it, and the stack trace is the story of how the program got there.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Error
- An object that describes something that went wrong, with a
name, amessageand astack. - Throw
- Stop the current work and hand an error to whatever code is waiting to deal with it.
- Uncaught error
- An error no code dealt with. The browser shows it in red starting with
Uncaughtand the script stops. - Parse
- Read the code and check its grammar before running any of it.
- SyntaxError
- The code or the text being read (like JSON) breaks the grammar rules.
- ReferenceError
- You used a name that doesn't exist at that point.
- TypeError
- The value exists but is the wrong kind for what you did, like calling something that isn't a function.
- RangeError
- A number is outside the range a method allows.
- Stack trace
- The list of function calls, newest first, that led to the error, with file, line and column.
Step by step
01An error is an ordinary object
Before thinking about throwing, look at what an error is. You can create one yourself with new TypeError("message"). It has a name, a message and a stack, and it is an instanceof Error.
The engine creates exactly the same kind of object when your code breaks. The only difference is that the engine also throws it.
const err = new TypeError("price must be a number");
console.log(err.name); // "TypeError"
console.log(err.message); // "price must be a number"
console.log(err instanceof TypeError); // true
console.log(err instanceof Error); // true: every error type inherits from Error02Sort the error by its name
The name is your first clue about where to look. Ask the questions in this order.
03Parse time or run time?
A SyntaxError in your own code is found before anything runs, so the console shows nothing from that script except the error. Every other error happens while running: the lines above it already ran, and their output is in the console.
This tells you a lot. If you see some of your logs and then an error, the problem is at run time and you can look at the values. If you see only the error, check the grammar.
// Script A: nothing prints. The parser rejects the whole file.
console.log("A starts");
let total = ;
// Script B: "B starts" prints, then the TypeError stops it.
console.log("B starts");
const user = null;
console.log(user.name);04Read the stack trace from the top
Here outer calls middle, which calls inner, which reads a property of undefined. The trace lists them newest first, with file, line and column. The last line has no function name: that's the top-level script that started it all.
Click any app.js:3:51 link in DevTools to jump to that exact spot in the Sources panel.
function outer() { middle(); }
function middle() { inner(); }
function inner() { const settings = undefined; return settings.theme; }
outer();05Find the real cause, not just the crash site
The top line says where it broke: inner read .theme from undefined. But the bug may be earlier: maybe middle should have passed settings in. Walk down the trace one line at a time and ask "was the value already wrong here?".
This is the most useful debugging habit there is: the line that crashes is usually the victim, not the culprit.
06Expect different wording in each browser
The name is the same everywhere because the ECMAScript standard defines it. The message is up to each engine. The same mistake reads Cannot read properties of null (reading 'x') in Chrome, el is null in Firefox and null is not an object (evaluating 'el.x') in Safari.
So never write code that checks the exact text of an engine's message. Check the type with instanceof or err.name instead.
Try it yourself
- 1
Predict the name before you look
In the first example, add
() => undefined()and() => "ab".repeat(-1)to the list. Predict the name of each error first, then press Run. - 2
Make a parse-time SyntaxError
In the third example, add a line
let broken = ;at the very end. Predict: doesLine 1 runsstill print? Run it and compare with what you saw before. Then remove the line. - 3
Add a level to the stack
In the second example, add a function
start()that callsouter(), and callstart()inside thetry. Predict the new list of names, then run it.
Code & diagrams
Printing an error shows `name: message`. The exact wording is Chrome's; Firefox and Safari use different messages for the same names.
Preview
Console
Nothing logged yet.Expected console output
ReferenceError: totl is not defined
TypeError: Cannot read properties of null (reading 'length')
TypeError: user.greet is not a function
TypeError: Assignment to constant variable.
RangeError: Invalid array length
RangeError: toFixed() digits argument must be between 0 and 100
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
URIError: URI malformedIn DevTools you don't need to parse the stack: it's printed under the error with clickable file and line links.
Preview
Console
Nothing logged yet.Expected console output
TypeError: Cannot read properties of undefined (reading 'theme')
Newest call first: [ 'inner', 'middle', 'outer' ]
Is it an Error? true | a TypeError? trueThe heading says "Line 2 ran": everything before the error happened, nothing after it did.
Preview
Console
Nothing logged yet.Expected console output
Line 1 runs
Uncaught TypeError: Cannot read properties of null (reading 'name')Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A typo in a variable name
Declare const total = 10; and then write console.log(totl * 2);.
Break #2
Call a method on the wrong kind of value
Write const count = "5"; count.toFixed(2); (a string, not a number).
Myth vs fact
Myth
An error means the whole page is broken.
Fact
An uncaught error stops the rest of that one script run (or that one event handler). HTML, CSS, other scripts and later events keep working. Read which script and line failed.
Myth
The line in the error is always where the bug is.
Fact
It's where the bad value was used. The bug is often earlier, where the value was created or passed in. Walk down the stack trace.
Myth
err.stack is part of the ECMAScript standard.
Fact
It isn't (yet), though every browser and Node.js support it. Its format differs: Chrome writes at fn (file:line:col), Firefox writes fn@file:line:col. Don't depend on its exact text.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
V8 keeps 10 stack frames by default. Raise it with
Error.stackTraceLimit = 50(V8 only) when a deep trace is cut off. DevTools also shows async stack traces: frames from before anawaitorsetTimeout, marked with the async boundary, which the plainerr.stackstring usually doesn't have. - ▸
Minified production code gives traces like
at a (app.min.js:1:20431). Source maps (.mapfiles made by your bundler, Topic 12.4) let DevTools and error-reporting services translate those back to your original file, function and line. - ▸
The standard defines the error constructors and when the engine must throw which type (for example, reading a property of
undefinedmust be a TypeError), but not the message text. That's why tests and code should checkinstanceoforname, never the engine's message. - ▸
Anything can be thrown, including strings and numbers, but only
Errorobjects get astack. A barethrow "oops"gives you no trace and no type to test withinstanceof. Topic 13.3 covers throwing your own errors properly.
Remember this
- 1
An error is an object that describes something that went wrong while JavaScript was reading or running your code. Every error has a
name(like"TypeError"), amessage(like"Cannot read properties of null (reading 'name')") and, in every browser and Node.js, astackstring listing the function calls that led to it. When the engine hits a problem it throws the error: it stops the current work and looks for code that will catch it. If nothing catches it, the error is uncaught: the browser prints it in red starting withUncaught, and the rest of that script stops. - 2
A SyntaxError means the grammar of the code is wrong: a missing bracket, a stray comma,
let x = ;. The engine finds it while parsing (reading the code before running it), so none of that script runs, not even the lines above the mistake. A SyntaxError can also happen at run time when your code asks JavaScript to read text as code or data:JSON.parse("{bad}")throws a SyntaxError because the text isn't valid JSON. Those run-time ones can be caught like any other error (Topic 13.2). - 3
A ReferenceError means you used a name that doesn't exist where you used it: a typo (
totlinstead oftotal), a wrong capital letter (Console), a variable used outside its scope, or alet/constvariable read before its line has run (the temporal dead zone from Topic 3.5, messageCannot access 'x' before initialization). The name itself is the problem, not its value. - 4
A TypeError means the name exists but its value is the wrong kind for what you did. The three you'll see most: reading a property of
nullorundefined(Cannot read properties of undefined (reading 'length')), calling something that isn't a function (user.greet is not a function), and changing something that can't be changed (Assignment to constant variable.). TypeErrors are the most common errors in real JavaScript, and most of them come from a value beingnullorundefinedwhen you expected an object. - 5
A RangeError means a number is outside the allowed range:
new Array(-1)(Invalid array length),(1.5).toFixed(200)(only 0 to 100 digits are allowed),"ab".repeat(-1), and the famousMaximum call stack size exceededfrom a recursive function with no base case (Topic 3.8). Rarer types: URIError (decodeURIComponent("%")→URI malformed), EvalError (kept only for old code), and AggregateError (ES2021), which holds a list of errors, for example when every promise passed toPromise.anyrejects (Topic 10.7). All of them inherit from the baseErrortype, soerr instanceof Erroristruefor every one. - 6
A stack trace is the list of function calls that were active when the error was created, newest first. In Chrome it looks like
at inner (app.js:3:51), thenat middle (app.js:2:23), thenat outer (app.js:1:22): the error happened ininner, which was called bymiddle, which was called byouter. Each line gives the function name, the file, the line and the column. Read it top-down: the top line is where it broke; the lines below explain how you got there. Often the real bug is a line or two down, where a bad value was passed in.
Explain it without notes
What are the main built-in error types, and what does each one tell you?
What is the difference between a SyntaxError in your script and one from JSON.parse?
How do you read a stack trace, and why isn't the top line always the bug?
Why shouldn't your code depend on the text of an error message from the engine?
Practice
Write code that triggers a RangeError from "ab".repeat(-1), catches it, and prints only its name.
This function crashes for one of the three users. Without running it, say which error and for which user, then fix it so it prints No email for that user: const users = [{ email: "a@x.in" }, { email: "b@x.in" }, {}]; users.forEach((u) => console.log(u.email.toLowerCase()));
Trade-offs
- ↔
Letting an error stay uncaught makes it loud and easy to notice while developing; in production it can leave a page half-updated, so the boundaries of your app (event handlers, data loading) usually catch and report errors instead.
Done when you can
I can name the four main error types and what each one means.
I can tell a parse-time SyntaxError from a run-time one.
I can read a stack trace from top to bottom and find the caller.
I check error types with
instanceoforname, not the message text.I know an uncaught error stops only the rest of that script run.