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.

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.

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.

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.

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.

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.

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.

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.

The four most common JavaScript error types and their typical causes.
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

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.

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
Step 1 — Identify the Error TypeThe 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
Step 2 — Read the Error MessageThe 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
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
Step 4 — Examine the CodeLooking 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
Step 5 — Fix the BugWe 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)

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.

Key differences between ReferenceError and TypeError
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 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.

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.

From basic error reading to professional debugging practices
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

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.

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.

PROBLEM 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?
PROBLEM 2 — BASIC CALCULATION
Look at this code and predict the exact error type and message that will appear: let greeting = 'Hello'; greeting();
PROBLEM 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)
PROBLEM 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.
PROBLEM 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?

Lesson 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