Reading error messages
Learn what JavaScript error names, messages, and stack traces are telling you so you can find bugs faster and search for help well.
- 01Name the kind of errorTell SyntaxError, ReferenceError, TypeError, RangeError, URIError, and EvalError apart, and say what each one means.
- 02Read a stack traceStart at the line where the error happened, then follow the calls back to see how the code got there.
- 03Search for helpPick the words from a message that everyone sees, search for them, then check the docs and your own code.
An error is a clue
Sometimes JavaScript reaches a line it cannot run. Then it stops and throws an error. That means it stops running your code and prints a report in the console, usually in red.
Red text can feel like "you failed". It is not. The report is a clue. It tells you what went wrong and where to look.
An error report has three parts:
- The name, like
TypeErrororReferenceError. It tells you the kind of mistake. - The message, like
Cannot read properties of undefined. It tells you which step failed. The wording can differ from browser to browser. - The stack trace. It is a list of the functions that were running, newest first. The top one is where the error happened.
A warning light in a car does not fix the car. But it tells you where to look: the oil, the brakes, or the battery. The light's name tells you which part. A short note on the screen tells you a bit more. Then the mechanic follows the wire to the part that broke.
- In real life: Which light is on, like the oil light
- In JavaScript: The error name, like
TypeError - In real life: The short note on the screen
- In JavaScript: The error message
- In real life: The mechanic follows the wire to the broken part
- In JavaScript: You follow the stack trace to your code
- Read the error name and the message.
- In the stack trace, find the first line that points to your own file.
- Open that file at that line.
- Ask: "What value did this line expect, and what did it get?"
You have already used the console, and you saw strict mode turn quiet mistakes into errors. This lesson teaches you to read those errors. The next lesson, Debugging in the browser, shows how to pause your code and look inside it.
SyntaxError, ReferenceError & TypeError
INTERACTIVEYou will meet three errors all the time. Each one answers a different question:
SyntaxError: "Can JavaScript read this text?" Syntax means the grammar of the code, like brackets and commas.ReferenceError: "Does this name exist?"TypeError: "Can this value do this?"
A SyntaxError does not only come from your code file. JSON is a text format for data. It looks a lot like a JavaScript object. JSON.parse reads JSON text and turns it into a value. It reads the text while your program runs. So bad JSON text can throw a SyntaxError even after your code has started.
The table below has one row per error. Read each row as: "this name means this, and here is a tiny example."
| Error name | What it means | A tiny example |
|---|---|---|
| SyntaxError | JavaScript cannot read the text, because its grammar is wrong. | JSON.parse("{bad json}") throws, because the JSON text is broken. |
| ReferenceError | Your code uses a name, but no variable has that name. | console.log(total) throws when no variable called total was made. |
| TypeError | The value exists, but it cannot do what the code asks. | undefined.name throws, because undefined has no properties. |
A missing property and a missing name are not the same thing. A property is a named value inside an object. An object can exist without a total property. Reading a property that isn't there gives undefined. It does not throw.
const cart = {};console.log(cart.total);Line 1 makes an empty object called cart. Line 2 reads its total property. There is none, so it prints undefined. This can cause an error later. For example, cart.total.price would try to read price from undefined, and that throws a TypeError.
A missing name is different. If no variable has the name, JavaScript cannot look it up at all, and it throws. The next example uses try and catch:
- JavaScript runs the code inside
try. - If that code throws, JavaScript jumps to
catch. The error is handed in aserror. error.nameis the error's name, anderror.messageis its message.
A later lesson covers try and catch fully. Here they only let us print the error's name.
try { console.log(total);} catch (error) { console.log(error.name);}Line 2 uses total, but no variable has that name. So JavaScript throws, and jumps to catch. Line 4 prints the error's name: ReferenceError. Remember the difference: cart.total is a missing property, and gives undefined. total on its own is a missing name, and throws.
Now a variable that exists, but holds a value that cannot do the job. Line 1 stores undefined in user. Line 2 tries to read the name property of that value. undefined has no properties, so JavaScript throws a TypeError. The exact message depends on your browser.
const user = undefined;user.name;total and finds none: a ReferenceError. On the right, the variable user exists, but the value inside it, undefined, has no name to read: a TypeError.Try each error in the lab below. Each button runs a small piece of code in your browser. The lab uses try and catch so it can show you error.name and error.message.
const demos = { reference() { console.log(total); }, type() { const user = undefined; return user.name; }, range() { return new Array(-1); }, uri() { return decodeURIComponent("%"); }, syntaxAtRuntime() { return JSON.parse("{bad json}"); }, evalCompatibility() { throw new EvalError("EvalError still exists"); },}; function inspectError(runDemo) { try { runDemo(); } catch (error) { return { name: error.name, message: error.message, }; }}ReferenceErrortotal is not definedA ReferenceError means your code used a name, but no variable has that name. Here nothing is called `total`.
Ask next: Did I spell this name right, and did I make the variable?
ReferenceError: total is not defined
Look at what stays the same. The name is the same in every browser. The message can be worded differently. Chrome and Node say Cannot read properties of undefined (reading 'name'). Firefox says it a different way. When a lesson shows an exact message, it says which browser it comes from.
So in a catch block, use error.name to check the kind of error. Read error.message as a helpful note for people. Don't write code that checks for one exact message, because another browser may word it differently.
try { JSON.parse("{bad json}");} catch (error) { console.log(error.name); console.log(typeof error.message);}Line 2 gives JSON.parse broken JSON text on purpose. It throws, and JavaScript jumps to catch. Line 4 prints SyntaxError. Line 5 uses typeof, which tells you what kind of value something is. It prints string, because the message is text. The example doesn't print the message itself, because its wording differs between browsers.
RangeError, URIError & EvalError
SORTYou will see the next three errors less often. Knowing them makes some scary messages easy to read.
One of them is about URIs. A URI is text that points to something, like a web address. Some characters can't go in a web address as they are, such as a space. So they are written as a code that starts with %. For example, a space is written %20. decodeURIComponent turns those codes back into normal text.
- RangeError means a number is too big or too small for the job.
new Array(-1)throws, because an array (a list of values) can't have a length below zero.(1).toFixed(101)throws too.toFixedrounds a number to a set count of decimal places, and that count must be from 0 to 100. In Chrome and Node the message saystoFixed() digits argument must be between 0 and 100. - URIError means the
%codes in the text are broken.%must be followed by a valid code. SodecodeURIComponent("%")throws, with the messageURI malformed. "Malformed" means "badly made". - EvalError is an old error. JavaScript today does not throw it on its own. It still exists so that old code keeps working. Your own code can still make one with
new EvalError("message").
Got a RangeError or a URIError? Try the same function with an input you know is valid. If that works, the function is fine, and only your input is wrong.
console.log((12.5).toFixed(2));console.log(decodeURIComponent("tea%20and%20toast"));Line 1 asks for two decimal places. That is between 0 and 100, so it prints 12.50. Line 2 turns each %20 back into a space and prints tea and toast. Valid inputs, so nothing throws.
You hand a paper form to a clerk at a desk. When something is wrong, the clerk tells you what kind of problem it is. Each kind tells you what to fix: your handwriting, the box you used, what you wrote, or how big the number is.
- In real life: The clerk can't read your handwriting
- In JavaScript:
SyntaxError: JavaScript can't read the grammar - In real life: You filled in a box the form doesn't have
- In JavaScript:
ReferenceError: no variable has that name - In real life: You wrote a word where a date should go
- In JavaScript:
TypeError: the value can't do this job - In real life: You wrote age 250
- In JavaScript:
RangeError: the number is outside the allowed range
Now sort some real messages by error name. Some messages come from built-in functions like JSON.parse, not from your own code's grammar. So always look at the line that threw, too.
- x is not defined
- Cannot read properties of undefined (reading 'name')
- Unexpected token
- Invalid array length
- Assignment to constant variable.
- Unexpected end of JSON input
- toFixed() digits argument must be between 0 and 100
Drag each message to the error name it usually comes with. If you are not sure, ask: what was the code trying to do?
- URI malformed
- URIError: malformed URI sequence
- Unexpected token
- Unexpected end of JSON input
Both errors come from reading text. Ask: is the text a web address with % codes, or is it JSON or code?
Reading a stack trace
STEP THROUGHA stack trace is the list of function calls that were running when the error happened. It is printed under the message. The newest call is at the top. Each line of the list is called a frame.
- The top frame answers: "Where did the error happen?"
- Each frame below it answers: "Who called the one above?"
A frame usually gives a function name, a file name, and a place in that file: file:line:column. For example, cart.js:10:32 means file cart.js, line 10, character 32 on that line.
You walk from room to room and drop a breadcrumb in each one. Then you trip. The crumb at your feet shows where you are now. The older crumbs show the way you came.
- In real life: The newest crumb, at your feet
- In JavaScript: The top frame: where the error happened
- In real life: Older crumbs lead back to the start
- In JavaScript: Lower frames: the calls that led there
- In real life: Each crumb is in a known room
- In JavaScript: Each frame gives
file:line:column
Step through this small checkout bug. The order has a total, but no discount. Watch the "Running in" list grow as each function calls the next one.
Step through the calls. Watch the Running in list grow: script, then placeOrder, then calculateTotal, then applyDiscount.
script
function placeOrder(order) { return calculateTotal(order);} function calculateTotal(order) { return applyDiscount(order.total, order.discount);} function applyDiscount(total, discount) { return total * (1 - discount.rate);} Now see a real stack trace from your own browser. The error object keeps it as text in error.stack. Your browser may write it a bit differently. The steps stay the same: read the message, find the top frame in your code, then check the value that line used.
function placeOrder(order) { return calculateTotal(order);} function calculateTotal(order) { return applyDiscount(order.total, order.discount);} function applyDiscount(total, discount) { return total * (1 - discount.rate);} placeOrder({ total: 40 });TypeError: Cannot read properties of undefined (reading 'rate')
at applyDiscount (cart.js:10:32)
at calculateTotal (cart.js:6:10)
at placeOrder (cart.js:2:10)
at cart.js:13:1Your browser may word it differently. Chrome and Node write each frame as at function (file:line:column). Firefox writes function@file:line:column.
Here is the same stack trace as Chrome and Node write it, with a note for each line. Chrome and Node both run JavaScript with the same engine, called V8. An engine is the part that runs your JavaScript. Firefox uses a different engine. It writes frames like applyDiscount@cart.js:10:32 instead.
TypeError: Cannot read properties of undefined (reading 'rate') at applyDiscount (cart.js:10:32) at calculateTotal (cart.js:6:10) at placeOrder (cart.js:2:10) at cart.js:13:1TypeError: Cannot read properties of undefined (reading 'rate')The error name and message. Start here.at applyDiscount (cart.js:10:32)Top frame: where the error happened.at calculateTotal (cart.js:6:10)Next frame: the function that called the one above.at placeOrder (cart.js:2:10)Keep reading down until you find your own code.at cart.js:13:1The oldest call: the line that started it all.
Searching for answers
WORKFLOWA web search can explain an error you have never seen. But the search works best with the right words. Error messages often contain your own names: your variables, your properties, your files. Nobody else uses those names. So keep the part of the message everyone sees, and leave out your own names.
const account = undefined;console.log(account.profile.name);Line 1 stores undefined in account. Line 2 tries to read account.profile, so it throws a TypeError. In Chrome the message is Cannot read properties of undefined (reading 'profile'). The word profile is your own name. The rest is the same for everyone.
- Copy the part of the message everyone sees:
Cannot read properties of undefined. - Put it in quotes, so the search looks for those exact words. Add the word
JavaScriptonly if you get too many results about other things. - Leave out your own names, like
accountandprofile. Add them back only if the results are not helpful. - Read MDN's page about JavaScript errors. Also read the docs for the function in the top frame of the stack trace.
- Make a small copy of the problem: the fewest lines of code that still throw the same error. Then explain each line out loud, as if to a friend. You will often spot the problem as you talk.
A good first search is "Cannot read properties of undefined", in quotes. Once you know what kind of error it is, go back to your code. Then ask the real question: "Why is this value undefined here?"
profile: other people's code uses different names.const errorName = "TypeError";const sharedMessage = "Cannot read properties of undefined";console.log(errorName + ' "' + sharedMessage + '"');Line 1 stores the error name. Line 2 stores the part of the message everyone sees, without your own names. Line 3 joins them, with the message in quotes. It prints TypeError "Cannot read properties of undefined".
Make a minimal reproduction
SMALL STEPSA minimal reproduction is the smallest piece of code that still shows the same error. "Minimal" means as small as possible. "Reproduction" means it makes the error happen again. With less code around it, it is easier to see which value is wrong.
To make one:
- Keep the line that fails, and the value it gets.
- Remove one part that seems unrelated.
- Run the code again.
- If the error is still there, repeat. If the error is gone, the part you removed mattered. Put it back and remove a different part.
function showProfileName(user) { return user.name;} showProfileName(undefined);Line 1 starts a function that expects a user object. Line 2 reads the user's name. Line 5 calls the function with undefined instead of a user. So line 2 tries to read name from undefined, and throws a TypeError. The rest of the web page is not needed to show this bug.
Now change only the value passed in. The function stays the same. Line 5 makes a user object. Line 6 calls the function and prints the name it returns: Asha.
function showProfileName(user) { return user.name;} const user = { name: "Asha" };console.log(showProfileName(user));Sometimes a missing user is normal. For example, a visitor who has not logged in has no profile. Then your code should show something sensible, like the word Guest. But if the profile should always be there, don't hide the problem. Find out why it is missing, and fix the code that passes it in.
function profileLabel(user) { if (user === undefined || user === null) { return "Guest"; } return user.name;} console.log(profileLabel(undefined));console.log(profileLabel(null));console.log(profileLabel({ name: "Asha" }));Line 1 starts a function. Line 2 checks for a missing user: is it undefined or null? (null is a value that means "nothing here, on purpose".) If so, line 3 returns Guest and the function stops. Line 5 reads the name only when a user is really there. The last three lines print Guest, Guest, and Asha, in that order.
There is a shorter way to write the same check, with two operators:
user?.namereadsnameonly ifuseris notnullorundefined. Otherwise it stops and givesundefined, without throwing.a ?? bgivesbwhenaisnullorundefined. Otherwise it givesa.
const user = undefined;console.log(user?.name ?? "Guest");Line 1 has no user. On line 2, user?.name stops and gives undefined. Then ?? gives "Guest" instead, so it prints Guest. Don't use this to hide data that must be there, such as the total of an order. Find out why that value is missing instead.
The step-through below follows the same kind of bug through two functions. The top frame is where the error happens. Then move down the stack trace to find where undefined came from.
This step-through follows a missing user from the call on the page to the line that throws. It is a recording of the steps, not a live debugger.
script
function showProfileName(user) { return user.name;} function renderProfile(user) { return showProfileName(user);} Where you’ll use this
You will read errors every day. A page does not load. A button does nothing. A test fails. Each time, these steps are usually the fastest way to the problem:
- Open the console. Find the first error that appeared after you clicked or loaded the page.
- Read the name and the message before you change any code.
- In the stack trace, find the first frame in your own file. Skip frames from browser add-ons or from code libraries you didn't write.
- Look at the values on that line. Is a name spelled wrong? Is a property missing? Is a number too big or too small?
- Still stuck? Search for the shared part of the message, and make a small copy of the problem.
The table below has one row per error name. Read each row as: "when I see this error, I ask this first, and the fix is often this."
| When you see | Ask first | The fix is often |
|---|---|---|
| ReferenceError | Did I spell this name right, and did I make the variable? | Fix the spelling, or make the variable where the code can see it. |
| TypeError | What value is really here? | Pass in the right value, or check for a missing value first. |
| SyntaxError | Is my code's grammar wrong, or is it JSON text? | Fix the code's grammar, or fix the broken JSON text. |
| RangeError | Which number is too big or too small? | Check the number before you use it, or pass a number in the allowed range. |
Common misconceptions
"The top frame of the stack trace always holds the real bug."
The top frame is where the error happened. But the cause can be earlier. A function lower in the stack may have passed in undefined, an empty string, or a wrong number.
"TypeError means I wrote the wrong type somewhere."
Plain JavaScript has no place to write types. A TypeError means a value, while the code ran, was asked to do something it cannot do.
"A SyntaxError always means my code file could not start."
Often it does. But some functions read text while your code runs, and they can throw a SyntaxError then. For example, JSON.parse("{bad json}").
"If my message is worded differently from a tutorial, it is a different bug."
Not always. Browsers word messages differently. Compare the error name and the step that failed first.
"It is best to search for the whole message."
Take out your own names first. Search the words everyone sees, in quotes. Add more details only if you need them.
The rules of JavaScript list the error names, like TypeError and RangeError. But they do not say what the English messages must say. That is why each browser can word them its own way.
Practice: read the error before fixing
6 EXERCISESThis code uses a name that no variable has. The catch block prints only the error's name, not the full message. Which name will it print? Decide before you run it.
try {
console.log(total);
} catch (error) {
console.log(error.name);
}No variable is called total, so line 2 throws. The catch block prints the error's name: ReferenceError.
The number exists, and the toFixed function exists. But the code asks for too many decimal places. The catch block prints the error's name. Which name will it print?
try {
(1).toFixed(101);
} catch (error) {
console.log(error.name);
}The number exists, and so does toFixed. But 101 decimal places is outside the allowed range, from 0 to 100. So the code prints RangeError.
This checkout code throws while it works out a discount. Picture its stack trace, newest call at the top. Which function holds the line where the error happens?
function placeOrder(order) {
return calculateTotal(order);
}
function calculateTotal(order) {
return applyDiscount(order.total, order.discount);
}
function applyDiscount(total, discount) {
return total * (1 - discount.rate);
}
placeOrder({ total: 40 });The error happens when line 10 reads discount.rate. That line is inside applyDiscount, so it is the top frame. calculateTotal and placeOrder called it, so they are lower in the stack trace.
The account object has no profile, so line 2 throws. Change line 1 so the account has a profile with the name Ada Lovelace. Don't change line 2. What does the fixed program print?
const account = {};
console.log(account.profile.name);const account = { profile: { name: "Ada Lovelace" } };
console.log(account.profile.name);The error happened because account.profile was undefined. Now account holds a profile object with a name, so line 2 can read account.profile.name. It prints Ada Lovelace.
You see this error: TypeError: Cannot read properties of undefined (reading 'name'). Search results are more helpful when you search the words everyone sees, not your own names. Write what you would search for first.
A good first search is "Cannot read properties of undefined". It keeps the words everyone sees and leaves out your own names. The results will explain this kind of TypeError.
A profile card on a website breaks while it shows a person's name. Here is a small copy of the bug. Find the missing value. Then change the data, not the function, so the card prints Asha.
function showProfileName(user) {
return user.name;
}
const user = undefined;
console.log(showProfileName(user));function showProfileName(user) {
return user.name;
}
const user = { name: "Asha" };
console.log(showProfileName(user));The profile card broke because user was undefined. Now user is an object with a name property, so the same function prints Asha.
Quiz: check your understanding
7 QUESTIONSSome questions ask about ideas. Others show a small program and ask what it prints. Each answer explains why it is right or wrong.
Question 1 of 7What is the first thing to read in an error report?
Choose an answer to see the explanation.
Question 2 of 7No variable is called
total. What does this code print?Read the code, then predicttry { console.log(total); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 3 of 7
userholdsundefined. What does this code print?Read the code, then predicttry { const user = undefined; console.log(user.name); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 4 of 7The JSON text is broken. What does this code print?
Read the code, then predicttry { JSON.parse("{bad json}"); } catch (error) { console.log(error.name); }Choose an answer to see the explanation.
Question 5 of 7In a stack trace, where is the line where the error happened?
Choose an answer to see the explanation.
Question 6 of 7What is the best first search query for
Cannot read properties of undefined (reading 'profile')?Choose an answer to see the explanation.
Question 7 of 7Which sentence about EvalError is true?
Choose an answer to see the explanation.
Key takeaways
- Read the error name first, then the message, then the stack trace.
SyntaxError: JavaScript can't read the grammar.ReferenceError: no variable has that name.TypeError: the value exists, but can't do what the code asks.RangeError: a number is too big or too small for the job.URIError: the%codes in a web address are broken.EvalError: an old error that JavaScript no longer throws on its own.- A stack trace lists calls newest first. Find the first frame in your own code, and check the values on that line.
- Search the words everyone sees, in quotes. Then read MDN, or the docs for the function in the top frame.
One-line definition.
An error message is JavaScript's report of what failed. A stack trace is the list of calls that led to it.
Coming next: Debugging in the browser.
console.log(total);const user = undefined;user.name;