Command Palette

Search for a command to run...

Hectal
PHASE 13Intermediate ~23 min· topic 1 of 6

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, a message and a stack.
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 Uncaught and 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.

error-object.jswhole filejavascript
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 Error

02Sort the error by its name

The name is your first clue about where to look. Ask the questions in this order.

Sort the error by its namediagram
Rendering diagram…

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.

parse-vs-run.jswhole filejavascript
// 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.

app.jswhole filejavascript
function outer() { middle(); }
function middle() { inner(); }
function inner() { const settings = undefined; return settings.theme; }

outer();
terminal
$ Console
── expected output ──
Uncaught TypeError: Cannot read properties of undefined (reading 'theme')
at inner (app.js:3:51)
at middle (app.js:2:21)
at outer (app.js:1:20)
at app.js:5:1

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. 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. 2

    Make a parse-time SyntaxError

    In the third example, add a line let broken = ; at the very end. Predict: does Line 1 runs still print? Run it and compare with what you saw before. Then remove the line.

  3. 3

    Add a level to the stack

    In the second example, add a function start() that calls outer(), and call start() inside the try. Predict the new list of names, then run it.

Code & diagrams

Every common error type, caught and printed New tab

Printing an error shows `name: message`. The exact wording is Chrome's; Firefox and Safari use different messages for the same names.

Preview

Press Run to see the page.

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 malformed
Reading a stack trace in code New tab

In DevTools you don't need to parse the stack: it's printed under the error with clickable file and line links.

Preview

Press Run to see the page.

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? true
An uncaught error stops the script New tab

The heading says "Line 2 ran": everything before the error happened, nothing after it did.

Preview

Press Run to see the page.

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);.

terminal
$ Console
── what you'll see ──
Uncaught ReferenceError: totl is not defined

Break #2

Call a method on the wrong kind of value

Write const count = "5"; count.toFixed(2); (a string, not a number).

terminal
$ Console
── what you'll see ──
Uncaught TypeError: count.toFixed is not a function

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 an await or setTimeout, marked with the async boundary, which the plain err.stack string usually doesn't have.

  • ▸

    Minified production code gives traces like at a (app.min.js:1:20431). Source maps (.map files 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 undefined must be a TypeError), but not the message text. That's why tests and code should check instanceof or name, never the engine's message.

  • ▸

    Anything can be thrown, including strings and numbers, but only Error objects get a stack. A bare throw "oops" gives you no trace and no type to test with instanceof. Topic 13.3 covers throwing your own errors properly.

Remember this

  1. 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"), a message (like "Cannot read properties of null (reading 'name')") and, in every browser and Node.js, a stack string 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 with Uncaught, and the rest of that script stops.

  2. 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. 3

    A ReferenceError means you used a name that doesn't exist where you used it: a typo (totl instead of total), a wrong capital letter (Console), a variable used outside its scope, or a let/const variable read before its line has run (the temporal dead zone from Topic 3.5, message Cannot access 'x' before initialization). The name itself is the problem, not its value.

  4. 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 null or undefined (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 being null or undefined when you expected an object.

  5. 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 famous Maximum call stack size exceeded from 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 to Promise.any rejects (Topic 10.7). All of them inherit from the base Error type, so err instanceof Error is true for every one.

  6. 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), then at middle (app.js:2:23), then at outer (app.js:1:22): the error happened in inner, which was called by middle, which was called by outer. 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

01

What are the main built-in error types, and what does each one tell you?

02

What is the difference between a SyntaxError in your script and one from JSON.parse?

03

How do you read a stack trace, and why isn't the top line always the bug?

04

Why shouldn't your code depend on the text of an error message from the engine?

Practice

01

Write code that triggers a RangeError from "ab".repeat(-1), catches it, and prints only its name.

02

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 instanceof or name, not the message text.

  • I know an uncaught error stops only the rest of that script run.