JS Error Handling & Debugging

Catch issues quickly and make your apps reliable.

try-catchthrowdebugger

Table of Contents

JavaScript Error Handling & Debugging: A Complete Tutorial

Errors are inevitable in programming. Great developers stand out by how they prevent, catch, investigate, and fix errors. This guide covers practical techniques to build reliable JavaScript applications.

1. Types of Errors in JavaScript

Syntax Errors

// console.log("Hello World"; // missing parenthesis
// const = 10; // invalid assignment
// function myFunction() { return true; // missing closing brace

Reference Errors

// console.log(x); // x is not defined
// console.log(myVar); let myVar = 5; // TDZ
const userName = "Alice";
// console.log(useName); // typo -> ReferenceError

Type Errors

// const user = null; console.log(user.name);
const PI = 3.14;
// PI = 3.14159; // Assignment to constant variable
// "Hello".push("World"); // not a function

Range, URI, Eval

// new Array(-1); // RangeError
// decodeURIComponent("%"); // URIError
try { throw new EvalError("Eval error message"); } catch (error) { console.log(error.name); }

Error Hierarchy

const errors = [new Error("Base"), new SyntaxError("Syntax"), new ReferenceError("Ref"), new TypeError("Type"), new RangeError("Range"), new URIError("URI")];
errors.forEach(err => console.log(err instanceof Error, err.name, err.message));

2. Try...Catch...Finally (The Basics)

Basic try/catch

try {
  const result = riskyOperation();
  console.log("Success:", result);
} catch (error) {
  console.error("Something went wrong:", error.message);
}

try/catch/finally

function processData(data) {
  let connection = null;
  try {
    connection = openDatabaseConnection();
    return connection.query("SELECT * FROM users");
  } catch (error) {
    console.error("Database error:", error);
    return null;
  } finally {
    if (connection) connection.close();
  }
}

Safe JSON Parse

function safeJSONParse(jsonString, fallback = null) {
  try { return JSON.parse(jsonString); }
  catch (error) { console.warn("Failed to parse JSON:", error.message); return fallback; }
}

Conditional Catch

function robustOperation(value) {
  try {
    if (typeof value !== "number") throw new TypeError("Value must be a number");
    if (value < 0) throw new RangeError("Value must be non-negative");
    return Math.sqrt(value);
  } catch (error) {
    if (error instanceof TypeError) return 0;
    if (error instanceof RangeError) return 0;
    return null;
  }
}

3. Error Objects and Custom Errors

try { throw new Error("Something went wrong"); }
catch (error) { console.log(error.name, error.message, error.stack); }

Custom Error Classes

class AppError extends Error {
  constructor(message, statusCode = 500) {
    super(message); this.name = this.constructor.name; this.statusCode = statusCode;
  }
}
class ValidationError extends AppError {
  constructor(message, field) { super(message, 400); this.field = field; }
}
class DatabaseError extends AppError {
  constructor(message, query) { super(message, 500); this.query = query; }
}

Error Cause (ES2022)

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) throw new Error(`HTTP ${response.status}`, { cause: { status: response.status, userId } });
    return await response.json();
  } catch (error) {
    throw new Error(`Failed to fetch user ${userId}`, { cause: error });
  }
}

4. Debugging Techniques

Strategic Logging

function processOrder(order) {
  console.log("[processOrder] start", { id: order.id, items: order.items.length });
  const validated = validateOrder(order);
  console.log("[processOrder] validated", validated);
  return saveOrder(validated);
}

Groups and Assertions

console.group("Complex Calculation");
console.assert(typeof price === "number", "price must be a number");
console.groupEnd();

Conditional Logging

const ENABLE_DEBUG = true;
function debugLog(...args) { if (ENABLE_DEBUG) console.log("[DEBUG]", ...args); }

5. Console API Mastery

console.log("Standard log");
console.info("Info");
console.warn("Warning");
console.error("Error");
console.table([{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]);
console.time("API Call");
console.timeEnd("API Call");
console.count("Processing user");
console.trace("Called here");

Custom Styled Logs

console.log("%cWarning", "color: orange; font-weight: bold");
const customConsole = {
  success: (...a) => console.log("%c✓ " + a[0], "color: green; font-weight: bold", ...a.slice(1)),
  error: (...a) => console.log("%c✗ " + a[0], "color: red; font-weight: bold", ...a.slice(1))
};

6. Breakpoints and Debugger Statement

function calculateTotal(items, taxRate) {
  let subtotal = 0;
  for (const item of items) {
    debugger;
    subtotal += item.price * item.quantity;
  }
  debugger;
  return subtotal + subtotal * taxRate;
}

Conditional Breakpoint Pattern

function processOrder(order) {
  if (order.total > 1000) debugger;
  if (order.userId === "problematic-user") debugger;
}

7. Common Errors and Solutions

TDZ / ReferenceError

let myVar = 5;
console.log(myVar);

Cannot read property of undefined/null

const user = getUser();
console.log(user?.name ?? "Anonymous");

X is not a function

if (typeof obj.getName === "function") obj.getName();

Maximum call stack size exceeded

function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

Network Robust Fetch

async function robustFetch(url, options = {}) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);
  try {
    const response = await fetch(url, { ...options, signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    return await response.json();
  } finally {
    clearTimeout(timeoutId);
  }
}

8. Error Logging and Reporting

Simple Error Logger

class ErrorLogger {
  constructor() { this.errors = []; this.maxStored = 100; }
  log(error, context = {}) {
    const entry = { timestamp: new Date().toISOString(), name: error.name, message: error.message, stack: error.stack, context };
    this.errors.unshift(entry);
    if (this.errors.length > this.maxStored) this.errors.pop();
  }
}

Global Handlers

window.addEventListener("error", event => {
  console.error("Unhandled error:", event.error);
});
window.addEventListener("unhandledrejection", event => {
  console.error("Unhandled promise rejection:", event.reason);
});

9. Best Practices and Patterns

Defensive Programming

function processUser(user) {
  if (!user || typeof user !== "object") throw new TypeError("User must be an object");
  if (!user.id || typeof user.id !== "number") throw new Error("User id must be numeric");
  return { ...user, displayName: user.name?.toUpperCase?.() ?? "UNKNOWN" };
}

Safe Object Access

const safeGet = (obj, path, defaultValue) => {
  try { return path.split(".").reduce((acc, key) => acc?.[key], obj) ?? defaultValue; }
  catch { return defaultValue; }
};

Async Error Wrapper

function asyncErrorHandler(asyncFn) {
  return (...args) => asyncFn(...args).catch(error => {
    console.error(`Error in ${asyncFn.name}:`, error);
    return null;
  });
}

Retry with Backoff

async function retryWithBackoff(asyncFunction, maxRetries = 3, initialDelay = 1000) {
  let lastError;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try { return await asyncFunction(); }
    catch (error) {
      lastError = error;
      if (attempt === maxRetries) break;
      await new Promise(r => setTimeout(r, initialDelay * Math.pow(2, attempt - 1)));
    }
  }
  throw new Error(`Failed after ${maxRetries} retries: ${lastError.message}`);
}

Summary: Error Handling & Debugging Quick Reference

Error Types

  • SyntaxError: code cannot be parsed.
  • ReferenceError: variable does not exist.
  • TypeError: invalid operation for given type.
  • RangeError: value out of allowed range.
  • URIError: URI handling issue.

Debugging Tools

ToolBest For
console.log()Quick checks and values
console.table()Object/array inspection
console.time()Performance timing
debuggerPaused execution context
BreakpointsComplex flow debugging
console.trace()Call stack trace

Best Practices Checklist

  • Validate function inputs.
  • Use optional chaining and nullish coalescing.
  • Handle Promise rejections.
  • Log with context, not generic messages.
  • Use custom error types for clear flows.
  • Avoid leaking sensitive data in error logs.
  • Gracefully degrade when features fail.
  • Use retry logic and request timeouts.
  • Report production errors to monitoring services.

Related: Asynchronous JS, Performance.

10 Error Handling Interview Q&A

10 Error Handling MCQs