How to Read an Error

Mental model: An error is a message with an address: the name says what kind, the stack says where.

Level: beginner · about 10 minutes

Errors are the most useful output your program produces. Every one has the same three parts, and once you can name them you stop reading errors as noise.

try {
  const user = null;
  console.log(user.name);
} catch (err) {
  console.log(err.name);          // → 'TypeError'
  console.log(err.message);       // → "Cannot read properties of null (reading 'name')"
  console.log(err.stack.split('\n')[0]); // → the first line of the trace
}
name
the category: TypeError, ReferenceError, SyntaxError, RangeError, or Error
message
the specific complaint, usually naming the value or property involved
stack
the call path, newest frame first, with file, line and column

Reading a stack trace

TypeError: Cannot read properties of undefined (reading 'total')
    at formatCart (cart.js:42:18)      ◄── where it blew up   (YOUR CODE)
    at renderPage (page.js:17:9)       ◄── who called that
    at handleClick (app.js:88:5)       ◄── who called that
    at HTMLButtonElement.onclick       ◄── the host, not you

   name ──► TypeError
message ──► Cannot read properties of undefined (reading 'total')
address ──► cart.js, line 42, column 18
  1. Read the name first It tells you the kind of mistake before you look at anything else. ReferenceError means a name problem. TypeError means the value was not what you assumed.
  2. Read the message literally It usually names the exact property or value. "reading total" means the thing to the left of .total was undefined, so look one step earlier than the line you are on.
  3. Find the first frame in your own code The top frame may be inside a library. Scan down to the first file you wrote. That is where your assumption broke.
  4. Print the thing that was wrong Do not guess. Log the value one line above the failure and confirm what it actually is.
  5. Fix the cause, not the symptom Adding ?. silences "cannot read properties of undefined" but leaves you with a missing cart. Ask why it was undefined.

The four names you will meet this week

NameMeansClassic trigger
SyntaxErrorthe code could not be parsed, so nothing rana missing bracket or quote
ReferenceErrorthat name does not exist herea typo, a missing import, or the wrong scope
TypeErrorthe value exists but is the wrong kind for what you didcalling a non-function, reading a property of undefined
RangeErrora legal kind of value, an illegal amount of itrunaway recursion, new Array(-1)
const cases = [
  () => JSON.parse('{oops}'),
  () => missingName,
  () => undefined.length,
  () => new Array(-1),
];

for (const attempt of cases) {
  try {
    attempt();
  } catch (err) {
    console.log(err.name);
  }
}
// → SyntaxError, ReferenceError, TypeError, RangeError

Four errors, four names. Note that JSON.parse throws a SyntaxError at runtime.

Message to cause, the short version

The message saysWhat actually happenedWhere to look
x is not definedno binding with that name is in scopespelling, a missing import, or a declaration inside another block
Cannot read properties of undefined (reading 'y')the value before .y was undefinedthe expression one step to the left, or the function that was meant to return it
x is not a functionthe name exists but holds something elsea typo in the method name, or a value you forgot to call or return
Cannot access 'x' before initializationa let or const was used above its declarationmove the declaration up; this is the temporal dead zone
Assignment to constant variableyou reassigned a constuse let, or mutate the object instead of replacing it
Unexpected token }brackets or quotes do not balancethe lines above the reported one
Maximum call stack size exceededa function called itself with no exitthe base case of your recursion
Converting circular structure to JSONthe object references itselfthe property that points back to a parent
Invalid array lengtha negative or fractional lengththe number you passed to new Array or length
console.log('one');
undefinedFunction();
console.log('two');

This is a runtime error, not a parse error, so line 1 runs and logs one. Then the missing name throws a ReferenceError and the script stops, so two never appears. Compare that with a SyntaxError, where you would see no output at all: what you did or did not see printed is itself a diagnostic.

Try it yourself

The error zoo

function tryIt(label, fn) {
  try {
    fn();
    console.log(label, '→ no error');
  } catch (err) {
    console.log(label, '→', err.name + ':', err.message);
  }
}

tryIt('parse bad JSON  ', () => JSON.parse('{oops}'));
tryIt('use a missing name', () => nothingHere);
tryIt('read from undefined', () => undefined.length);
tryIt('bad array length ', () => new Array(-1));
tryIt('perfectly fine   ', () => [1, 2].map((n) => n * 2));

Add a fifth case that throws a RangeError by recursing forever. Then throw your own error with throw new Error('with a useful message').

Exercises

Classify the failure

Write classify(fn) which calls fn and returns the name of the error it threw, as a string. If fn runs without throwing, return 'ok'. Do not let any error escape.

Throw an error worth reading

Write parseAge(input) which accepts a string of digits and returns it as a number. Throw a TypeError if input is not a string, or is a string that is not entirely digits. Throw a RangeError if the number is above 130. Every message must include the value you rejected, so the person reading the stack trace does not have to guess.

Check yourself

What does this log?
undefined — Reading a property that does not exist gives undefined rather than an error, so the typo in lenght is silent. This is why the error you eventually see is often several lines away from the mistake: the undefined travels before it breaks something.
user is {}. What does user.save() throw?
TypeError: user.save is not a function — user is found, so it is not a reference problem. user.save evaluates to undefined, and calling undefined is a type problem, hence TypeError. The name of the error tells you which half of the expression to distrust.
You reload the page and none of your console.log calls appear, not even the first. What is the most likely cause?
A SyntaxError, so the file never ran — Parsing happens before any execution, so a syntax problem anywhere in the file stops the whole file. Total silence is the signature. A runtime error, by contrast, lets everything above it run first.

Common mistakes

  • Reading only the message and skipping the name. The name is the fastest half of the diagnosis.
  • Debugging the top frame of the stack when it belongs to a library. Scroll to the first line of your own code.
  • Patching Cannot read properties of undefined with ?. and calling it fixed, when the real question is why the value was missing.

Takeaways

  • Every error has a name (what kind), a message (the specific complaint) and a stack (where).
  • ReferenceError means the name is missing; TypeError means the value cannot do what you asked.
  • A SyntaxError prevents the whole file from running, so silence is the clue.
  • Log the suspect value before changing code, and search the message with your own identifiers removed.