JavaScript • Debugging and Testing

JavaScript Errors & Stack Traces — Interpret common JS errors (ReferenceError, TypeError) and stack traces

Learn to read error messages like a detective and fix bugs faster in your JavaScript code.

JavaScript emblem with code brackets, braces, and a code file.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

When Brendan Eich created JavaScript in just ten days in 1995, he built a language that would eventually run on billions of devices. But even from the earliest days, programmers needed a way to know when something went wrong. A runtime error is the program's way of saying, "I tried to do what you asked, but I hit a wall." Without clear error messages, debugging code would be like trying to find a typo in a book with no page numbers — incredibly frustrating.

Early programming languages often crashed silently or gave cryptic numeric codes when something broke. JavaScript's designers learned from these mistakes and built a structured error system that tells you what went wrong, where it happened, and why. Over time, browser developer tools evolved to present this information in increasingly helpful ways, making error messages and stack traces essential tools for every developer.

1995 • JavaScript Is Born

Brendan Eich creates JavaScript for Netscape Navigator. Basic error handling exists, but developer tools are primitive — errors often appear as pop-up alerts.

1999 • ECMAScript 3 Standardizes Errors

The ECMAScript 3 specification formalizes built-in error types including ReferenceError, TypeError, and SyntaxError, giving developers consistent error categories across browsers.

2006 • Firebug Revolutionizes Debugging

The Firebug extension for Firefox introduces a proper console and stack trace viewer, making it far easier to track down the source of errors in complex web applications.

2010 • Chrome DevTools Leads the Way

Google Chrome's built-in developer tools offer clickable stack traces, inline error highlighting, and rich console output that become the gold standard for JavaScript debugging.

2015–Today • Modern Error Handling

ES6 and beyond bring improved error messages, source maps for compiled code, and professional error-tracking services like Sentry that monitor errors in production applications.

Today, understanding error messages and stack traces is one of the most practical skills a programmer can develop. The question every developer faces is: when your code breaks, can you quickly identify what went wrong and where to fix it? That is exactly what this lesson will teach you.

SECTION 2

Core Principles of JavaScript Errors

Every JavaScript error follows a predictable structure. When the JavaScript engine encounters a problem it cannot resolve, it creates an Error object containing three key pieces of information: the error type (what category of problem occurred), the error message (a human-readable description), and the stack trace (a trail showing exactly which functions were called leading up to the error). Understanding these three components is the foundation of effective debugging.

1

Error Type

The category of the error, such as ReferenceError or TypeError. This tells you the general nature of the problem — did you use a wrong name, or try an impossible operation?
2

Error Message

A plain-English description of the specific problem, like x is not defined or foo is not a function. Read this carefully — it usually names the variable or value that caused the issue.
3

Stack Trace

A reverse-chronological list of function calls that led to the error. The top of the stack shows where the error occurred; the bottom shows where execution began. Think of it as breadcrumbs back to the source of the problem.
4

Thrown vs. Caught

When an error is thrown, it stops normal execution. If wrapped in a try...catch block, the error is caught and the program can recover gracefully instead of crashing.
✦

KEY TAKEAWAY

Think of a JavaScript error like a car's dashboard warning light. The error type is the icon (engine, oil, battery), telling you the category of the problem. The error message is the text display explaining the issue. And the stack trace is like the mechanic's diagnostic log, showing every step that led to the warning. A good driver doesn't ignore dashboard lights — a good programmer doesn't ignore error messages.

SECTION 3

Anatomy of an Error Message

Let's break down what a JavaScript error looks like in a browser console. The following diagram shows the three major parts of an error output and how they relate to each other. When you see an error in your console, train yourself to read it from top to bottom: identify the type, read the message, then trace the stack.

Anatomy of a JavaScript ErrorUncaught ReferenceError: userName is not defined at greetUser (app.js:12:15)① ERROR TYPEReferenceErrorCategory of the problem.② ERROR MESSAGEuserName is not definedSpecific description of what failed.③ STACK TRACEat greetUser (app.js:12:15)File, line, and column number.Reading the Stack Trace (bottom → top = earliest → latest): 3. at greetUser (app.js:12:15) ← Error occurred HERE 2. at startApp (app.js:20:3) ← Called greetUser() 1. at <anonymous> (app.js:25:1) ← Program entry pointRead from bottom to top to follow the call path
This diagram shows a typical error message from the browser console. The error type appears first, followed by the error message, and the stack trace below, which you read from bottom to top to follow the path of execution.

Notice how the stack trace reads like a trail of function calls. The bottom entry shows where the program started running, and each line above it shows a function that was called by the one below. The very top line is where the error actually happened. The numbers after the file name — like 12:15 — represent the line number and column number in your source code. In most browser consoles, you can click on these to jump directly to that location in the code editor.

SECTION 4

How JavaScript Errors Work Under the Hood

To truly understand errors, you need to know a bit about how JavaScript executes your code. JavaScript uses something called the call stack — a data structure that keeps track of which function is currently running and which functions called it. Every time you call a function, JavaScript adds a new frame to the top of the stack. When that function finishes, the frame is removed. When an error occurs, JavaScript captures the current state of this call stack — that snapshot becomes the stack trace you see in the console.

The Error Propagation Process

When an error is thrown, JavaScript doesn't just stop immediately. It follows a specific process called error propagation. First, it checks if the current function has a try...catch block that can handle the error. If not, it "unwinds" the call stack, moving down to the calling function and checking there. This continues until either a catch block is found, or the error reaches the top level of the program, at which point JavaScript logs the error to the console and stops execution.

Call Stack & Error PropagationSTEP 1: Building the Stack<anonymous>startApp()greetUser()← TOP (most recent)← BOTTOM (entry point)STEP 2: Error Thrown!<anonymous>startApp()greetUser()💥 ReferenceError!STEP 3: Unwinding<anonymous>startApp()greetUser()errorbubblesdownWhat Happens If No catch Block Is Found?1. Error propagates through every function on the call stack2. Reaches the global scope with no handler3. JavaScript logs the error + stack trace to the console and halts✓ Wrap risky code in try...catch to handle errors gracefully
The call stack builds as functions are called (Step 1). When an error is thrown (Step 2), JavaScript checks each frame for a catch block while unwinding the stack (Step 3). If none is found, the error appears in the console.
💡

Try...Catch Syntax

You can prevent an error from crashing your program by wrapping risky code in a try { ... } catch (error) { ... } block. The catch block receives the error object, letting you log it, display a user-friendly message, or take corrective action. This is especially useful when working with user input or network requests that might fail.

SECTION 5

Common JavaScript Error Types

JavaScript has several built-in error types, but you will encounter two far more often than the rest: ReferenceError and TypeError. Knowing the difference between these errors instantly narrows down where to look for a bug. Let's examine each error type, along with SyntaxError and RangeError, which you will also see from time to time.

Error TypeWhen It HappensCommon CauseExample Message
ReferenceErrorYou use a variable or function name that doesn't exist in the current scope.Typo in a variable name, using a variable before declaring it, or the variable is out of scope.usrName is not defined
TypeErrorYou perform an operation on a value of the wrong type.Calling something that isn't a function, accessing a property on null or undefined.Cannot read properties of undefined (reading 'name')
SyntaxErrorYour code has a grammar mistake that prevents JavaScript from parsing it at all.Missing parentheses, brackets, or semicolons; mismatched quotes.Unexpected token '}'
RangeErrorA value is outside the allowed range.Infinite recursion (a function that calls itself forever), or passing invalid arguments to built-in methods.Maximum call stack size exceeded

The four most common JavaScript error types and their typical causes.

ReferenceError: Deep Dive

A ReferenceError means JavaScript looked for a variable or function name and couldn't find it anywhere in the current scope chain. This is almost always caused by one of three things: you misspelled the name (like writing userNme instead of userName), you tried to use a variable before declaring it with let or const, or the variable was declared inside a function or block and you're trying to access it from outside.

TypeError: Deep Dive

A TypeError means a value exists, but you tried to do something with it that doesn't work for its type. The most common scenario is trying to access a property or method on undefined or null. For example, if you have an object user that hasn't been loaded yet and you write user.name, JavaScript will throw a TypeError because you can't read the name property of undefined. Another frequent cause is trying to call a value as a function when it isn't one, like writing myVar() when myVar is a string or number.

ℹ

Quick Diagnostic

"is not defined" → the name doesn't exist → ReferenceError. "is not a function" or "Cannot read properties of..." → the name exists but holds the wrong kind of value → TypeError. Memorize this distinction and you'll save yourself a lot of debugging time.

SECTION 6

Worked Example: Debugging a Real Error

Let's walk through a realistic debugging scenario step by step. Imagine you've written a small program that greets a user, but when you run it, you see this error in the console:

⚠

Console Output

Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase') at formatName (script.js:3:17) at greet (script.js:8:20) at <anonymous> (script.js:12:1)

Debugging a TypeError Step by Step

  1. 1

    Step 1 — Identify the Error Type

    The error says TypeError. This means a value exists but has the wrong type for what we're trying to do. We're not dealing with a missing variable (that would be a ReferenceError).
    Error Type: TypeError → wrong type, not a missing name
  2. 2

    Step 2 — Read the Error Message

    The message says Cannot read properties of undefined (reading 'toUpperCase'). This tells us we tried to call .toUpperCase() on something that was undefined. The value we thought was a string is actually undefined.
    Clue: .toUpperCase() was called on undefined, not a string
  3. 3

    Step 3 — Read the Stack Trace (Top Line First)

    The top line of the stack trace says at formatName (script.js:3:17). This tells us the error occurred in the formatName function, on line 3, column 17 of script.js. Let's go look at that line.
    Location: formatName function → script.js, line 3, column 17
  4. 4

    Step 4 — Examine the Code

    Looking at the code, we find: function formatName(name) { return name.toUpperCase(); } and function greet(user) { console.log('Hello, ' + formatName(user.firstName)); } and greet({ name: 'Alice' }); We see the problem: we passed an object with a name property, but the greet function is looking for user.firstName. Since firstName doesn't exist on the object, it evaluates to undefined.
    Root cause: user.firstName is undefined because the property is named 'name', not 'firstName'
  5. 5

    Step 5 — Fix the Bug

    We have two options: change the function to use user.name instead of user.firstName, or change the object to include a firstName property. The right choice depends on your program's design. Either way, the fix ensures that formatName receives a string, not undefined.
    Fix: greet({ firstName: 'Alice' }) or change to formatName(user.name)

SECTION 7

ReferenceError vs. TypeError: Side-by-Side

Students often confuse ReferenceErrors and TypeErrors because both can involve variables that don't hold the expected value. The key distinction is whether the name itself is unknown (ReferenceError) or the name exists but holds the wrong type of value (TypeError). The table below compares these two error types across several dimensions to help you tell them apart instantly.

FeatureReferenceErrorTypeError
Core ProblemA name that JavaScript can't find in any accessible scopeA value that exists but doesn't support the operation you're attempting
Typical Messagex is not definedx is not a function or Cannot read properties of undefined
Most Common FixCheck spelling, make sure the variable is declared, verify scopeCheck what value the variable actually holds; add null/undefined checks
Debugging TipSearch your file for the exact variable name — does it appear in a let or const declaration?Add console.log(typeof x) before the failing line to see the actual type
AnalogyLooking for a contact named 'Bob' in your phone — but no one named Bob existsYou found Bob's contact, but you try to send a text to his street address instead of his phone number

Key differences between ReferenceError and TypeError

✦

KEY TAKEAWAY

Here's a quick mental test: if the error message says "is not defined", it's a ReferenceError — the variable name simply doesn't exist. If it says "is not a function" or "Cannot read properties of", it's a TypeError — the variable exists but holds a value you didn't expect. Think of it like GPS navigation: a ReferenceError means the address doesn't exist on any map, while a TypeError means the address exists but it's a lake, not a road.

SECTION 8

Connections to Advanced Error Handling

The error reading skills you've learned here form the foundation for more advanced debugging and error-handling techniques. As your programs grow in complexity — especially when they involve fetching data from servers, interacting with databases, or running code asynchronously — the errors become trickier, but the fundamental approach remains the same: read the type, read the message, trace the stack.

What You Know NowWhat Comes Next
Reading error messages in the consoleUsing breakpoints and the Chrome DevTools debugger to pause execution and inspect variables at any point
Using try...catch blocksCreating custom error classes that extend the built-in Error object for application-specific error handling
Understanding synchronous stack tracesReading async stack traces from Promise rejections and async/await code
Debugging errors one at a timeWriting automated tests (unit tests) that catch bugs before they reach users
Fixing bugs in local developmentUsing error monitoring services like Sentry to track and analyze errors from real users in production

From basic error reading to professional debugging practices

One particularly important concept to watch for is the unhandled promise rejection. When you start writing asynchronous JavaScript code using async and await, errors inside async functions need their own try...catch blocks. Modern browsers will warn you with messages like Unhandled Promise Rejection — and the debugging approach is the same: read the type, read the message, trace the stack.

SECTION 9

Practice Problems

Test your understanding of JavaScript errors and stack traces with these five problems. They increase in difficulty, so start from the top and work your way down.

Question 1: CONCEPTUAL

You see the following error in the console: Uncaught ReferenceError: score is not defined. What does this tell you about the variable score? What are two possible reasons this error could occur?
Show answer
This error tells you that JavaScript searched for a variable named score in all accessible scopes and could not find it. Two common reasons: (1) the variable name is misspelled — perhaps you wrote score but declared it as Score or scor (JavaScript is case-sensitive). (2) The variable was declared inside a different function or block (like inside an if statement using let or const) and you're trying to access it from outside that block.

Question 2: BASIC CALCULATION

Look at this code and predict the exact error type and message that will appear: let greeting = 'Hello'; greeting();
Show answer
This code will produce: Uncaught TypeError: greeting is not a function. The variable greeting exists and holds the string 'Hello', so it's not a ReferenceError. However, the code tries to call it as a function using (). Since a string is not a function, JavaScript throws a TypeError. The fix would be to either define greeting as a function or remove the parentheses.

Question 3: INTERMEDIATE

Given this stack trace, answer: (a) What function did the error occur in? (b) Which function called it? (c) What line should you examine first? TypeError: Cannot read properties of null (reading 'length') at countItems (utils.js:15:22) at processOrder (shop.js:42:10) at handleClick (app.js:7:3)
Show answer
(a) The error occurred in the countItems function — it's at the top of the stack trace. (b) processOrder called countItems — it's one level below in the trace. (c) Examine line 15 of utils.js first, where something is null instead of an array or string. Then check line 42 of shop.js to see what value was passed to countItems — that's likely where the null value originated.

Question 4: APPLIED

You're building a weather app. The following code sometimes works and sometimes throws an error. Explain why, and write a fix using a defensive check: function displayTemp(data) { let temp = data.main.temp; document.getElementById('temp').textContent = temp + '°F'; } Sometimes data comes back as { error: 'City not found' } from the API.
Show answer
When the API returns { error: 'City not found' }, the object has no main property, so data.main is undefined. Then data.main.temp throws a TypeError because you can't read .temp on undefined. A fix: if (data && data.main && data.main.temp !== undefined) { ... } else { console.log('No temperature data available'); }. You could also use optional chaining: let temp = data?.main?.temp; which safely returns undefined instead of throwing an error.

Question 5: CRITICAL THINKING

Consider this scenario: you have a large codebase with 20 JavaScript files. You see the error TypeError: callback is not a function with a stack trace three levels deep. The top of the stack shows the error in a utility function that many parts of the app use. Describe a systematic debugging strategy. What would you check first, second, and third? Why might adding console.log(typeof callback) be more helpful than console.log(callback) in this case?
Show answer
First, read the stack trace to identify which caller passed the bad value — the second line of the stack tells you which function called the utility function that threw the error. Go to that line and inspect what's being passed as the callback argument. Second, add console.log(typeof callback) at the top of the utility function to confirm the actual type (you might see 'undefined', 'string', or 'object'). This is more helpful than logging the value directly because console.log(undefined) just prints blank/undefined, but typeof tells you the exact type, narrowing down the bug. Third, add a defensive check at the beginning of the utility function: if (typeof callback !== 'function') { console.error('Expected a function but got:', typeof callback); return; }. This pattern is called a guard clause, and it prevents the crash while giving you a clear diagnostic message that will help you find every calling location that passes an incorrect argument.

SUMMARY

Lesson Summary

Summary

JavaScript errors are structured messages with three key parts: the error type (like ReferenceError or TypeError), the error message describing the specific problem, and the stack trace showing the chain of function calls that led to the error. A ReferenceError means a variable or function name doesn't exist in the current scope — check for typos and scope issues. A TypeError means a value exists but doesn't support the operation you're trying to perform — usually because something is undefined or null when you expected an object, string, or function.

When debugging, always follow the same process: read the error type to identify the category, read the message for specifics, then read the stack trace from top to bottom to find the exact line and function where the error occurred. Use console.log() and typeof to inspect values, and wrap risky code in try...catch blocks to handle errors gracefully. These skills are the foundation of all JavaScript debugging and will serve you throughout your programming career.

Varsity Tutors • JavaScript • JavaScript Errors & Stack Traces