Common JavaScript Errors and Solutions

Understanding and Fixing Frequent Mistakes

Welcome to Error Diagnosis 101

Every developer encounters errors - it's like being a detective where the clues are error messages and the mystery is finding the bug. Today, we'll explore the most common JavaScript errors, understand why they happen, and learn how to fix them. By the end of this lesson, you'll be able to diagnose and resolve errors like a seasoned developer!

The Error Hierarchy

graph TD A[JavaScript Errors] --> B[Syntax Errors] A --> C[Runtime Errors] A --> D[Logical Errors] B --> B1[SyntaxError: Unexpected token] B --> B2[SyntaxError: Missing brackets/parentheses] C --> C1[ReferenceError: Variable not defined] C --> C2[TypeError: Cannot read property of undefined] C --> C3[RangeError: Maximum call stack exceeded] D --> D1[Incorrect business logic] D --> D2[Wrong algorithm implementation] style B fill:#ff6b6b,stroke:#333 style C fill:#4ecdc4,stroke:#333 style D fill:#ffe66d,stroke:#333

1. SyntaxError: Unexpected Token

This error occurs when JavaScript encounters code it doesn't understand.

Common Example:

// ERROR: Missing closing bracket
function calculateTotal(items) {
    let total = 0;
    for (let item of items) {
        total += item.price;
    // Missing closing bracket!
    return total;
}

// ERROR: Unexpected token
const user = {
    name: "John",
    age: 30,
    address: "123 Main St",  // Extra comma
};

// ERROR: Invalid JSON
const data = JSON.parse('{"name": "John", "age": 30,}'); // Trailing comma

SyntaxError: Unexpected token }

Solution:

// CORRECT: All brackets properly closed
function calculateTotal(items) {
    let total = 0;
    for (let item of items) {
        total += item.price;
    }
    return total;
}

// CORRECT: No trailing comma
const user = {
    name: "John",
    age: 30,
    address: "123 Main St"
};

// CORRECT: Valid JSON
const data = JSON.parse('{"name": "John", "age": 30}');

// TIP: Use JSON.stringify for safe JSON creation
const safeJson = JSON.stringify({ name: "John", age: 30 });

2. ReferenceError: Variable Not Defined

This occurs when you try to use a variable that hasn't been declared.

Common Examples:

// ERROR: Variable used before declaration
console.log(username);
let username = "Alice";

// ERROR: Misspelled variable name
let userCount = 10;
console.log(usercount); // lowercase 'c'

// ERROR: Variable out of scope
function doSomething() {
    let localVar = "I'm local";
}
console.log(localVar); // Can't access function variable

// ERROR: Forgot to declare variable
for (i = 0; i < 5; i++) { // 'i' not declared
    console.log(i);
}

ReferenceError: username is not defined

Solutions:

// CORRECT: Declare before use
let username = "Alice";
console.log(username);

// CORRECT: Use correct variable name
let userCount = 10;
console.log(userCount); // Correct case

// CORRECT: Return value from function
function doSomething() {
    let localVar = "I'm local";
    return localVar;
}
console.log(doSomething());

// CORRECT: Always declare variables
for (let i = 0; i < 5; i++) {
    console.log(i);
}

// TIP: Use 'use strict' to catch undeclared variables
'use strict';
// Now this will throw an error:
// myVar = 10; // Error: myVar is not defined

3. TypeError: Cannot Read Property of Undefined/Null

This happens when you try to access a property of undefined or null.

Common Examples:

// ERROR: Accessing property of undefined
let user;
console.log(user.name);

// ERROR: Chaining properties that might not exist
let data = { user: { /* name might be missing */ } };
console.log(data.user.name.length);

// ERROR: Array method on undefined
let items;
items.forEach(item => console.log(item));

// ERROR: Calling method on null
let element = document.getElementById('nonexistent');
element.addEventListener('click', handler);

TypeError: Cannot read property 'name' of undefined

Solutions:

// SOLUTION 1: Check for existence
let user;
if (user) {
    console.log(user.name);
}

// SOLUTION 2: Optional chaining (?.)
let data = { user: {} };
console.log(data.user?.name?.length);

// SOLUTION 3: Provide default values
let items = [];
items.forEach(item => console.log(item));

// SOLUTION 4: Null checking
let element = document.getElementById('nonexistent');
if (element) {
    element.addEventListener('click', handler);
}

// SOLUTION 5: Nullish coalescing (??)
const username = user?.name ?? 'Guest';

// SOLUTION 6: Defensive programming
function getProperty(obj, path) {
    return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}

console.log(getProperty(data, 'user.name.length')); // Safe access

4. TypeError: X is Not a Function

This occurs when you try to call something that isn't a function.

Common Examples:

// ERROR: Calling a non-function value
let result = "hello";
result(); // Strings aren't functions

// ERROR: Overwriting a function with a value
let calculate = (a, b) => a + b;
calculate = calculate(5, 3); // Now calculate is 8, not a function
calculate(2, 2); // Error!

// ERROR: Incorrect method name
let numbers = [1, 2, 3];
numbers.maps(n => n * 2); // Should be 'map'

// ERROR: Forgetting parentheses in method call
let user = {
    getName: function() {
        return this.name;
    }
};
console.log(user.getName); // Returns function, doesn't call it

TypeError: result is not a function

Solutions:

// CORRECT: Only call functions
let result = () => "hello";
result(); // This works

// CORRECT: Don't overwrite functions
let calculate = (a, b) => a + b;
let result = calculate(5, 3); // Store result separately
console.log(calculate(2, 2)); // Function still works

// CORRECT: Use correct method names
let numbers = [1, 2, 3];
numbers.map(n => n * 2); // Correct method name

// CORRECT: Call the function
let user = {
    getName: function() {
        return this.name;
    }
};
console.log(user.getName()); // Note the parentheses

// TIP: Check if something is a function before calling
if (typeof myVar === 'function') {
    myVar();
}

5. RangeError: Maximum Call Stack Size Exceeded

This happens when you have infinite recursion or very deep function calls.

Common Examples:

// ERROR: Infinite recursion
function countDown(n) {
    console.log(n);
    countDown(n - 1); // No base case!
}
countDown(10);

// ERROR: Circular references in recursion
function processNode(node) {
    processNode(node.parent); // What if parent references child?
}

// ERROR: Event handler triggering itself
element.addEventListener('click', function() {
    element.click(); // Triggers the same handler!
});

// ERROR: Deep recursion without tail call optimization
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(1000); // Too deep!

RangeError: Maximum call stack size exceeded

Solutions:

// SOLUTION 1: Add base case
function countDown(n) {
    if (n <= 0) return; // Base case
    console.log(n);
    countDown(n - 1);
}

// SOLUTION 2: Check for circular references
function processNode(node, visited = new Set()) {
    if (!node || visited.has(node)) return;
    visited.add(node);
    processNode(node.parent, visited);
}

// SOLUTION 3: Prevent recursive event triggering
let isProcessing = false;
element.addEventListener('click', function() {
    if (isProcessing) return;
    isProcessing = true;
    // Do work
    isProcessing = false;
});

// SOLUTION 4: Use iteration instead of recursion
function fibonacciIterative(n) {
    if (n <= 1) return n;
    let a = 0, b = 1;
    for (let i = 2; i <= n; i++) {
        [a, b] = [b, a + b];
    }
    return b;
}

// SOLUTION 5: Memoization for recursive functions
function fibonacciMemo(n, memo = {}) {
    if (n in memo) return memo[n];
    if (n <= 1) return n;
    memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
    return memo[n];
}

6. TypeError: Assignment to Constant Variable

This occurs when you try to reassign a const variable.

Common Examples:

// ERROR: Reassigning const
const PI = 3.14159;
PI = 3.14; // Error!

// ERROR: Reassigning const in loop
const scores = [85, 92, 78];
for (const score of scores) {
    score = score + 5; // Error!
}

// ERROR: Const with undefined initial value
const username;
username = "Alice"; // Error!

TypeError: Assignment to constant variable

Solutions:

// SOLUTION 1: Use let for variables that change
let PI = 3.14159;
PI = 3.14; // This works

// SOLUTION 2: Use let in loops when modifying values
const scores = [85, 92, 78];
let adjustedScores = [];
for (let score of scores) {
    adjustedScores.push(score + 5);
}

// SOLUTION 3: Initialize const variables immediately
const username = "Alice";

// SOLUTION 4: Const objects can have properties modified
const user = { name: "Alice" };
user.name = "Bob"; // This is allowed
user.age = 30; // This is allowed

// SOLUTION 5: Use Object.freeze for immutable objects
const frozenUser = Object.freeze({ name: "Alice" });
// frozenUser.name = "Bob"; // This will fail silently (or error in strict mode)

7. Async/Await Errors

Common mistakes when working with asynchronous code.

Common Examples:

// ERROR: Forgot to use await
async function fetchData() {
    const response = fetch('/api/data'); // Missing await
    const data = response.json(); // This will fail
    return data;
}

// ERROR: Using await outside async function
function regularFunction() {
    const data = await fetchData(); // Error!
}

// ERROR: Not handling promise rejections
async function riskyOperation() {
    const data = await fetchData(); // What if this fails?
    console.log(data);
}

// ERROR: Parallel execution turned sequential
async function fetchMultiple() {
    const user = await fetchUser();
    const posts = await fetchPosts();
    const comments = await fetchComments();
    // These run sequentially, not in parallel
}

SyntaxError: await is only valid in async function

Solutions:

// SOLUTION 1: Always use await with promises
async function fetchData() {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

// SOLUTION 2: Make function async to use await
async function correctFunction() {
    const data = await fetchData();
    return data;
}

// SOLUTION 3: Handle errors with try/catch
async function safeOperation() {
    try {
        const data = await fetchData();
        console.log(data);
    } catch (error) {
        console.error('Operation failed:', error);
    }
}

// SOLUTION 4: Run promises in parallel
async function fetchMultipleParallel() {
    const [user, posts, comments] = await Promise.all([
        fetchUser(),
        fetchPosts(),
        fetchComments()
    ]);
    return { user, posts, comments };
}

// SOLUTION 5: Global error handler for unhandled rejections
process.on('unhandledRejection', (error) => {
    console.error('Unhandled promise rejection:', error);
});

8. Common Logical Errors

These don't throw errors but cause incorrect behavior.

Common Examples:

// ERROR: Comparing with assignment
if (x = 5) { // Always true, assigns 5 to x
    console.log("This always runs");
}

// ERROR: String concatenation instead of addition
console.log("2" + 2); // "22", not 4

// ERROR: Floating point precision
console.log(0.1 + 0.2 === 0.3); // false!

// ERROR: Array/Object comparison
console.log([1, 2] === [1, 2]); // false
console.log({a: 1} === {a: 1}); // false

// ERROR: Incorrect this binding
const obj = {
    value: 42,
    getValue: function() {
        return this.value;
    }
};
const getValue = obj.getValue;
console.log(getValue()); // undefined

Solutions:

// SOLUTION 1: Use comparison operator
if (x === 5) {
    console.log("x equals 5");
}

// SOLUTION 2: Convert strings to numbers
console.log(Number("2") + 2); // 4
console.log(parseInt("2") + 2); // 4

// SOLUTION 3: Handle floating point precision
function areFloatsEqual(a, b, epsilon = 0.00001) {
    return Math.abs(a - b) < epsilon;
}
console.log(areFloatsEqual(0.1 + 0.2, 0.3)); // true

// SOLUTION 4: Compare array/object contents
function arraysEqual(a, b) {
    return a.length === b.length && 
           a.every((val, index) => val === b[index]);
}

function objectsEqual(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}

// SOLUTION 5: Bind this correctly
const getValue = obj.getValue.bind(obj);
console.log(getValue()); // 42

// Or use arrow function in object
const obj2 = {
    value: 42,
    getValue: () => {
        return this.value; // Note: this doesn't work as expected in arrow functions
    }
};

// Better approach
class MyClass {
    constructor() {
        this.value = 42;
    }
    
    getValue = () => {
        return this.value; // Arrow function preserves this
    }
}

Error Prevention Strategies

1. Use Strict Mode

'use strict';

// Prevents accidental globals
mistypedVariable = 17; // Error in strict mode

// Prevents duplicate parameters
function sum(a, a, c) { // Error in strict mode
    return a + a + c;
}

2. Type Checking

function safeOperation(value) {
    // Check types before operations
    if (typeof value !== 'number') {
        throw new TypeError('Expected a number');
    }
    
    return value * 2;
}

// Use TypeScript or JSDoc for better type safety
/**
 * @param {number} a
 * @param {number} b
 * @returns {number}
 */
function add(a, b) {
    return a + b;
}

3. Defensive Programming

function processData(data) {
    // Validate input
    if (!data || typeof data !== 'object') {
        console.error('Invalid data provided');
        return null;
    }
    
    // Safe property access
    const name = data.user?.name ?? 'Unknown';
    const age = data.user?.age ?? 0;
    
    // Handle edge cases
    if (age < 0 || age > 150) {
        console.warn('Suspicious age value:', age);
    }
    
    return { name, age };
}

Debugging Strategies

graph TD A[Error Occurs] --> B{Read Error Message} B --> C[Identify Error Type] C --> D[Locate Error Line] D --> E[Understand Context] E --> F[Check Variables] F --> G[Test Fix] G --> H{Error Fixed?} H -->|No| I[Try Alternative Solution] I --> E H -->|Yes| J[Document Solution]

Systematic Debugging Process:

  1. Read the error message carefully
  2. Identify the file and line number
  3. Understand what the code is trying to do
  4. Check variable values at the error point
  5. Reproduce the error consistently
  6. Form a hypothesis about the cause
  7. Test your hypothesis
  8. Implement and verify the fix

Common Anti-Patterns to Avoid

// ANTI-PATTERN 1: Swallowing errors
try {
    riskyOperation();
} catch (e) {
    // Don't do this!
}

// BETTER: Always handle or log errors
try {
    riskyOperation();
} catch (error) {
    console.error('Operation failed:', error);
    // Handle the error appropriately
}

// ANTI-PATTERN 2: Using exceptions for flow control
function findUser(id) {
    throw new Error('User not found'); // Don't use exceptions for expected cases
}

// BETTER: Return null or use a result object
function findUser(id) {
    return users.find(u => u.id === id) || null;
}

// ANTI-PATTERN 3: Overly broad error catching
try {
    // Lots of different operations
    operation1();
    operation2();
    operation3();
} catch (e) {
    console.log('Something went wrong');
}

// BETTER: Specific error handling
try {
    operation1();
} catch (e) {
    handleOperation1Error(e);
}

try {
    operation2();
} catch (e) {
    handleOperation2Error(e);
}

Practice Exercises

Exercise 1: Debug This Code

// This code has multiple errors. Find and fix them:
function calculateAverage(numbers) {
    let sum;
    for (let i = 0; i <= numbers.length; i++) {
        sum += numbers[i];
    }
    return sum / numbers.length;
}

const scores = [85, 92, 78, 90];
console.log("Average:", calculateAverage(scores));

Exercise 2: Error Handling Challenge

// Add proper error handling to this function:
async function fetchUserProfile(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    
    const posts = await fetch(`/api/users/${userId}/posts`);
    const userPosts = await posts.json();
    
    return {
        user: user,
        posts: userPosts,
        totalLikes: userPosts.reduce((sum, post) => sum + post.likes, 0)
    };
}

Key Takeaways

Additional Resources