Common JavaScript Errors and How to Fix Them

8 min read

Most beginners spend their first months chasing the same handful of errors over and over. This lesson is the cheat sheet — ten error messages you'll hit repeatedly, what each one means, the buggy code that causes it, and the fix. Internalise these and the cost of writing code drops dramatically: error messages stop being scary and start being directions.

How to read a JavaScript error

Every JavaScript error gives you four pieces of information. Read them in this order:

  1. The error typeTypeError, ReferenceError, SyntaxError. Tells you the category.
  2. The message — the human description of what went wrong.
  3. The file and line number — where the error was thrown.
  4. The stack trace — the call path that led to the throw.

Don't skim the message. JavaScript tells you what's wrong — usually quite precisely. The fixes below assume you've read the message; you'll often find you already know the answer once you do.

1. "Cannot read properties of undefined (reading 'X')"

The single most common runtime error. You're accessing a property on a value that's undefined.

const user = users.find(u => u.id === 99);   // 99 doesn't exist → undefined
console.log(user.name);                       // ❌ TypeError

Fix: check the variable first, or use optional chaining:

console.log(user?.name);                      // ✅ logs undefined, no crash
if (!user) return console.warn("not found");  // ✅ explicit guard
console.log(user.name);

2. "X is not a function"

You're calling something that isn't a function. Two common causes:

const filter = users.filter(...);   // overwrote `filter` accidentally
filter.toUpperCase();                // ❌ filter is now an array, not a function
 
const cb = "submit";
cb();                                // ❌ "submit" isn't a function

Fix: rename the shadowing variable, or fix the value before calling. A second flavour of this error: passing arguments in the wrong order, so a string lands where a callback was expected — see error 7.

3. "Unexpected token"

A SyntaxError — JavaScript can't even parse your code. Usually a missing or extra bracket, brace, comma, or quote.

const config = {
  baseUrl: "https://staging.com",
  timeout: 5000     // missing closing brace below
const next = 1;     // ❌ Unexpected token 'const'

Fix: check the line before the line in the error. Editors with bracket-matching (VS Code) make this trivial — highlight a brace and the matching one lights up. Auto-formatters (Prettier) also surface unbalanced braces immediately.

4. "X is not defined"

ReferenceError — JavaScript doesn't know the name X. Three causes, in order of probability:

console.log(usrs);       // ❌ typo for users
console.log(token);      // ❌ declared inside another function (scope issue)
console.log(_);          // ❌ lodash not imported

Fix: check spelling, scope, and imports. The temporal dead zone from chapter 3 is also a possibility — using let or const before the declaration line.

5. "Cannot assign to constant variable"

You declared with const but tried to reassign:

const maxRetries = 3;
maxRetries = 5;   // ❌ TypeError: Assignment to constant variable

Fix: use let if the value genuinely changes:

let maxRetries = 3;
maxRetries = 5;   // ✅

Note: const only stops reassignment. Mutating the contents of an object or array is allowed (chapter 3 covers this in detail).

6. JSON.parse errors

Almost always means the input isn't valid JSON. The most common shapes:

JSON.parse('{ "a": 1, }');           // ❌ trailing comma
JSON.parse("{ 'a': 1 }");            // ❌ single quotes
JSON.parse('{ "a": undefined }');    // ❌ undefined isn't valid JSON
JSON.parse("{ a: 1 }");              // ❌ unquoted key

Fix: validate the JSON. Paste it into the JSON Formatter utility on qa.codes — it points at the precise byte that broke the parse. In code, wrap the parse in try/catch (lesson 1) so a bad fixture fails clearly instead of crashing the whole script.

7. "Callback is not a function"

You passed something that wasn't a function where a callback was expected — usually because you got the parameter order wrong:

fs.readFile("utf-8", "users.json", (err, data) => { ... });
//          ^^^^^^^^  ^^^^^^^^^^^^   the args are swapped
// → TypeError: cb is not a function

Fix: check the function's signature. fs.readFile(path, encoding, callback) — path first, encoding second, callback last. The error message names the parameter that wasn't a function — that's where to look in your call.

8. Off-by-one in loops

Not an explicit error, but the most common behavioural bug — looping one too many or one too few times.

for (let i = 0; i <= testCases.length; i++) {  // ❌ <=, reads past the end
  testCases[i].run();                            // ❌ TypeError on the final iteration
}

Fix: the canonical loop bound is i < array.length (strict less-than). For positional access, remember arrays are 0-indexed. Better yet, use for...of or forEach and avoid the index entirely.

9. Forgotten await

The async equivalent of "where's my data?" Without await, the variable holds a Promise object instead of the resolved value.

async function loadUsers() {
  const users = fetch("/api/users").then(r => r.json());   // ❌ missing await
  console.log(users.length);                                // ❌ users is a Promise, no .length
}

Fix: add the await. When in doubt, await. (Chapter 5's full coverage.)

const users = await fetch("/api/users").then(r => r.json());
console.log(users.length);                                  // ✅

10. Comparing objects/arrays with ===

Two different object literals that look identical are still different objects — === compares references, not contents.

console.log({ a: 1 } === { a: 1 });       // ❌ false (different objects in memory)
console.log([1, 2, 3] === [1, 2, 3]);     // ❌ false

Fix: compare individual properties, or stringify both sides and compare the strings (good for shallow data, dangerous for objects with functions or undefined):

const a = { id: 1, role: "admin" };
const b = { id: 1, role: "admin" };
 
console.log(a.id === b.id && a.role === b.role);                  // ✅
console.log(JSON.stringify(a) === JSON.stringify(b));             // ✅ for plain data

For deep comparison in tests, frameworks provide their own matchers — Jest's expect(a).toEqual(b), Cypress's .should("deep.equal", b). Prefer those over hand-rolled comparisons.

Practice — match each error to its fix

Match each error to the right fix

For each error, choose the most appropriate fix.

  • TypeError: Cannot read properties of undefined (reading 'name')
  • TypeError: filter.toUpperCase is not a function
  • ReferenceError: usrs is not defined
  • TypeError: Assignment to constant variable
  • SyntaxError: Unexpected token in JSON at position 22
  • TypeError: Cannot read properties of undefined (reading 'length') after fetch()

 

⚠️ Common mistakes

  • Skipping past the error message. Cannot read properties of undefined (reading 'name') tells you exactly which property and which line. Beginners often glance and re-google; reading the message saves the search.
  • Treating every error as a code bug. Sometimes the error is a real signal — a missing fixture, a broken API contract, a malformed network response. Don't silence the error with a try/catch until you understand whether it's a bug to fix or a failure to handle.
  • Trusting Stack Overflow over the actual error. A 10-year-old answer to a different version of a similar error message will lead you astray. Read the message, read the stack, then search.

🎯 Practice task

Reproduce and fix each error. 25-30 minutes.

  1. In your js-for-qa folder, create bugs.js. For each of the ten errors above, write one tiny snippet that triggers it, run it, capture the exact error message and line, and add a one-line comment with the fix:

    // 1. TypeError: Cannot read properties of undefined
    const u = undefined;
    // console.log(u.name);  // crashes — fix: u?.name
    console.log(u?.name);
  2. After all ten, you'll have a personal reference file you can consult forever.

  3. Stretch: add an eleventh — pick one error you've actually hit in your own work that isn't in the list, write the snippet, capture the message, and document the fix.

That ends chapter 7. You can now write code that handles failures intentionally, debug it with structured tools instead of guesswork, and read JavaScript's error messages as the directions they actually are. The final chapter applies everything you've learned to a single, real test utility — the capstone project.

// tip to track lessons you complete and pick up where you left off across devices.