JavaScript Error Handling and Debugging

1. Error Types (Error, TypeError, ReferenceError, SyntaxError) and Properties

Built-in Error Types

Error Type Description Common Causes
Error Generic error (base class) Manually thrown errors
SyntaxError Invalid JavaScript syntax Parse errors, malformed code
ReferenceError Invalid reference to variable Undefined variables, accessing before declaration
TypeError Value is not expected type Calling non-function, accessing property on null/undefined
RangeError Value not in allowed range Invalid array length, recursion limit
URIError Invalid URI encoding/decoding encodeURI/decodeURI with malformed URI
EvalError Error in eval() (legacy) Rarely used in modern JavaScript
AggregateError ES2021 Multiple errors wrapped together Promise.any() rejection, multiple failures

Error Object Properties

Property Description Standard
name Error type name (e.g., "TypeError") ✓ Standard
message Human-readable error description ✓ Standard
stack Stack trace (non-standard but widely supported) ✗ Non-standard
cause ES2022 Underlying error that caused this error ✓ Standard
fileName File where error occurred (Firefox) ✗ Non-standard
lineNumber Line number where error occurred (Firefox) ✗ Non-standard
columnNumber Column number where error occurred (Firefox) ✗ Non-standard

Error Constructor Options

Constructor Syntax Parameters
new Error() new Error(message, options) message: string, options: {cause}
message Error description String
options.cause Original error that caused this error Any (typically Error object)

Example: Built-in error types

// SyntaxError (parse-time error, not catchable at runtime)
// eval('var x = ;');  // SyntaxError: Unexpected token ';'

// ReferenceError
try {
    console.log(nonExistentVariable);
} catch (e) {
    console.log(e.name);      // "ReferenceError"
    console.log(e.message);   // "nonExistentVariable is not defined"
}

// TypeError
try {
    const obj = null;
    obj.someMethod();
} catch (e) {
    console.log(e.name);      // "TypeError"
    console.log(e.message);   // "Cannot read properties of null"
}

try {
    const notAFunction = 42;
    notAFunction();
} catch (e) {
    console.log(e.name);      // "TypeError"
    console.log(e.message);   // "notAFunction is not a function"
}

// RangeError
try {
    const arr = new Array(-1);
} catch (e) {
    console.log(e.name);      // "RangeError"
    console.log(e.message);   // "Invalid array length"
}

function recursiveFunction() {
    recursiveFunction();
}

try {
    recursiveFunction();
} catch (e) {
    console.log(e.name);      // "RangeError"
    console.log(e.message);   // "Maximum call stack size exceeded"
}

// URIError
try {
    decodeURIComponent('%');
} catch (e) {
    console.log(e.name);      // "URIError"
    console.log(e.message);   // "URI malformed"
}

// AggregateError (ES2021)
const errors = [
    new Error('Error 1'),
    new Error('Error 2'),
    new Error('Error 3')
];

const aggregateError = new AggregateError(errors, 'Multiple errors occurred');

console.log(aggregateError.name);     // "AggregateError"
console.log(aggregateError.message);  // "Multiple errors occurred"
console.log(aggregateError.errors);   // Array of 3 errors

aggregateError.errors.forEach((err, i) => {
    console.log(`Error ${i + 1}:`, err.message);
});

Example: Error object properties

// Creating error manually
const error = new Error('Something went wrong');

console.log(error.name);     // "Error"
console.log(error.message);  // "Something went wrong"
console.log(error.stack);    // Stack trace (multi-line string)

// Stack trace example
function a() {
    b();
}

function b() {
    c();
}

function c() {
    throw new Error('Error in function c');
}

try {
    a();
} catch (e) {
    console.log(e.stack);
    // Error: Error in function c
    //     at c (file.js:10:11)
    //     at b (file.js:6:5)
    //     at a (file.js:2:5)
    //     at file.js:14:5
}

// Error cause (ES2022)
async function fetchData(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return await response.json();
    } catch (originalError) {
        // Wrap original error with context
        throw new Error('Failed to fetch data', {
            cause: originalError
        });
    }
}

try {
    await fetchData('https://invalid-url');
} catch (error) {
    console.log(error.message);        // "Failed to fetch data"
    console.log(error.cause);          // Original fetch error
    console.log(error.cause.message);  // Original error message
}

// Error with custom properties
const customError = new Error('Custom error');
customError.code = 'ERR_CUSTOM';
customError.statusCode = 400;
customError.timestamp = Date.now();

console.log(customError.code);        // "ERR_CUSTOM"
console.log(customError.statusCode);  // 400
console.log(customError.timestamp);   // Unix timestamp

Example: Parsing stack traces

// Parse stack trace for debugging
function parseStackTrace(error) {
    if (!error.stack) return [];
    
    const lines = error.stack.split('\n');
    const frames = [];
    
    // Skip first line (error message)
    for (let i = 1; i < lines.length; i++) {
        const line = lines[i].trim();
        
        // Example format: "at functionName (file.js:10:5)"
        const match = line.match(/at (.+?) \((.+?):(\d+):(\d+)\)/);
        
        if (match) {
            frames.push({
                functionName: match[1],
                fileName: match[2],
                lineNumber: parseInt(match[3], 10),
                columnNumber: parseInt(match[4], 10)
            });
        }
    }
    
    return frames;
}

try {
    throw new Error('Test error');
} catch (e) {
    const frames = parseStackTrace(e);
    console.log('Stack frames:', frames);
    
    // Print formatted stack
    frames.forEach((frame, i) => {
        console.log(
            `  ${i + 1}. ${frame.functionName}`,
            `at ${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`
        );
    });
}

// Capture stack trace programmatically
function captureStack() {
    const obj = {};
    Error.captureStackTrace(obj, captureStack);
    return obj.stack;
}

console.log(captureStack());
Key Points: JavaScript has 8 built-in error types. All errors have name and message properties. stack property (non-standard but universal) provides call stack. ES2022 cause option enables error chaining. AggregateError (ES2021) wraps multiple errors.

2. Custom Error Classes and Error Inheritance

Custom Error Pattern

Step Requirement Implementation
1. Extend Error Inherit from Error class class CustomError extends Error
2. Set name Set error name to class name this.name = this.constructor.name
3. Call super() Initialize Error with message super(message)
4. Capture stack Fix stack trace (optional) Error.captureStackTrace(this, this.constructor)
5. Add properties Add custom data this.code = code

Common Custom Error Types

Error Type Use Case Additional Properties
ValidationError Input validation failures field, value, rule
NotFoundError Resource not found (404) resourceType, resourceId
UnauthorizedError Authentication failure (401) userId, reason
ForbiddenError Authorization failure (403) action, resource
NetworkError Network/HTTP failures statusCode, url, method
TimeoutError Operation timeout duration, operation
DatabaseError Database operation failures query, code

Example: Basic custom error class

// Simple custom error
class CustomError extends Error {
    constructor(message) {
        super(message);
        this.name = this.constructor.name;
        
        // Maintains proper stack trace (V8 only)
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

// Usage
try {
    throw new CustomError('Something went wrong');
} catch (e) {
    console.log(e instanceof CustomError);  // true
    console.log(e instanceof Error);        // true
    console.log(e.name);                    // "CustomError"
    console.log(e.message);                 // "Something went wrong"
}

// Custom error with additional properties
class ValidationError extends Error {
    constructor(message, field, value) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
        this.value = value;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

function validateAge(age) {
    if (typeof age !== 'number') {
        throw new ValidationError(
            'Age must be a number',
            'age',
            age
        );
    }
    
    if (age < 0 || age > 150) {
        throw new ValidationError(
            'Age must be between 0 and 150',
            'age',
            age
        );
    }
    
    return true;
}

try {
    validateAge('twenty');
} catch (e) {
    if (e instanceof ValidationError) {
        console.log(`Validation failed for field "${e.field}"`);
        console.log(`Invalid value: ${e.value}`);
        console.log(`Error: ${e.message}`);
    }
}

try {
    validateAge(200);
} catch (e) {
    console.log(e.field);    // "age"
    console.log(e.value);    // 200
    console.log(e.message);  // "Age must be between 0 and 150"
}

Example: Domain-specific error classes

// HTTP error hierarchy
class HttpError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'HttpError';
        this.statusCode = statusCode;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

class NotFoundError extends HttpError {
    constructor(resource, id) {
        super(`${resource} with id ${id} not found`, 404);
        this.name = 'NotFoundError';
        this.resource = resource;
        this.id = id;
    }
}

class UnauthorizedError extends HttpError {
    constructor(message = 'Unauthorized') {
        super(message, 401);
        this.name = 'UnauthorizedError';
    }
}

class ForbiddenError extends HttpError {
    constructor(action, resource) {
        super(`Not allowed to ${action} ${resource}`, 403);
        this.name = 'ForbiddenError';
        this.action = action;
        this.resource = resource;
    }
}

// Usage
async function getUser(userId) {
    const user = await db.findUser(userId);
    
    if (!user) {
        throw new NotFoundError('User', userId);
    }
    
    return user;
}

async function deletePost(userId, postId) {
    const post = await db.findPost(postId);
    
    if (!post) {
        throw new NotFoundError('Post', postId);
    }
    
    if (post.authorId !== userId) {
        throw new ForbiddenError('delete', 'post');
    }
    
    await db.deletePost(postId);
}

// Error handling
try {
    await getUser(999);
} catch (e) {
    if (e instanceof NotFoundError) {
        console.log(`Status: ${e.statusCode}`);
        console.log(`${e.resource} ${e.id} not found`);
    } else if (e instanceof HttpError) {
        console.log(`HTTP Error ${e.statusCode}: ${e.message}`);
    }
}

Example: Advanced error patterns

// Error with error codes
class ApplicationError extends Error {
    constructor(message, code, details = {}) {
        super(message);
        this.name = 'ApplicationError';
        this.code = code;
        this.details = details;
        this.timestamp = new Date().toISOString();
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
    
    toJSON() {
        return {
            name: this.name,
            message: this.message,
            code: this.code,
            details: this.details,
            timestamp: this.timestamp
        };
    }
}

// Error factory
class ErrorFactory {
    static validation(field, message) {
        return new ApplicationError(
            message,
            'VALIDATION_ERROR',
            {field}
        );
    }
    
    static notFound(resource, id) {
        return new ApplicationError(
            `${resource} not found`,
            'NOT_FOUND',
            {resource, id}
        );
    }
    
    static network(url, statusCode) {
        return new ApplicationError(
            'Network request failed',
            'NETWORK_ERROR',
            {url, statusCode}
        );
    }
}

try {
    throw ErrorFactory.validation('email', 'Invalid email format');
} catch (e) {
    console.log(e.code);              // "VALIDATION_ERROR"
    console.log(e.details.field);     // "email"
    console.log(JSON.stringify(e));   // Full error as JSON
}

// Async error wrapper
class AsyncOperationError extends Error {
    constructor(message, operation, originalError) {
        super(message);
        this.name = 'AsyncOperationError';
        this.operation = operation;
        this.originalError = originalError;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

async function wrapAsync(operation, fn) {
    try {
        return await fn();
    } catch (error) {
        throw new AsyncOperationError(
            `Failed to ${operation}`,
            operation,
            error
        );
    }
}

try {
    await wrapAsync('fetch user data', async () => {
        throw new Error('Network timeout');
    });
} catch (e) {
    console.log(e.message);                    // "Failed to fetch user data"
    console.log(e.operation);                  // "fetch user data"
    console.log(e.originalError.message);      // "Network timeout"
}

// Retryable error
class RetryableError extends Error {
    constructor(message, retryAfter = 1000) {
        super(message);
        this.name = 'RetryableError';
        this.retryAfter = retryAfter;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

async function fetchWithRetry(url, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await fetch(url);
        } catch (error) {
            if (attempt === maxRetries) {
                throw error;
            }
            
            throw new RetryableError(
                `Attempt ${attempt} failed`,
                1000 * attempt
            );
        }
    }
}
Best Practices: Extend Error class for custom errors. Set name property to class name. Use Error.captureStackTrace for clean stack traces. Add domain-specific properties (field, statusCode, code). Create error hierarchies for different error categories. Implement toJSON() for serialization.

3. Exception Propagation and Error Boundaries

Exception Propagation Rules

Context Behavior Handling
Synchronous code Propagates up call stack try-catch in caller
Promise chain Propagates through .then() chain .catch() or try-catch with await
Async function Returns rejected promise try-catch or .catch()
Event handlers Does not propagate to caller Handle in event handler or window.onerror
Callbacks Does not propagate to caller Error-first callback pattern
setTimeout/setInterval Does not propagate Handle in callback or window.onerror

Error Boundary Patterns

Pattern Scope Use Case
try-catch Block-level Synchronous code, async/await
.catch() Promise chain Promise-based operations
finally Cleanup after try-catch Resource cleanup (always runs)
window.onerror Global (window) Uncaught exceptions
window.onunhandledrejection Global (promises) Unhandled promise rejections
process.on('uncaughtException') Global (Node.js) Uncaught exceptions in Node
process.on('unhandledRejection') Global (Node.js promises) Unhandled rejections in Node

Error Recovery Strategies

Strategy Description When to Use
Fail fast Throw immediately, don't catch Programming errors, invalid state
Retry Attempt operation again Transient failures (network, rate limits)
Fallback Use alternative approach/data Non-critical features, degraded mode
Circuit breaker Stop trying after repeated failures External service failures
Log and continue Record error but don't stop Non-critical operations
Graceful degradation Reduce functionality but keep working Feature failures in production

Example: Exception propagation patterns

// Synchronous propagation
function level3() {
    throw new Error('Error at level 3');
}

function level2() {
    level3();  // Error propagates up
}

function level1() {
    try {
        level2();
    } catch (e) {
        console.log('Caught at level 1:', e.message);
    }
}

level1();  // "Caught at level 1: Error at level 3"

// Async propagation with promises
function asyncLevel3() {
    return Promise.reject(new Error('Async error'));
}

function asyncLevel2() {
    return asyncLevel3();  // Rejection propagates
}

function asyncLevel1() {
    return asyncLevel2()
        .catch(e => {
            console.log('Caught in promise chain:', e.message);
        });
}

asyncLevel1();

// Async/await propagation
async function asyncAwaitLevel3() {
    throw new Error('Async/await error');
}

async function asyncAwaitLevel2() {
    await asyncAwaitLevel3();  // Error propagates
}

async function asyncAwaitLevel1() {
    try {
        await asyncAwaitLevel2();
    } catch (e) {
        console.log('Caught with async/await:', e.message);
    }
}

asyncAwaitLevel1();

// Event handler - no propagation
document.getElementById('button').addEventListener('click', () => {
    throw new Error('Error in event handler');
    // This will NOT be caught by outer try-catch
});

// Must handle in event handler
document.getElementById('button').addEventListener('click', () => {
    try {
        // risky operation
        throw new Error('Handled error');
    } catch (e) {
        console.log('Caught in handler:', e.message);
    }
});

// Callback - no propagation
function asyncOperation(callback) {
    setTimeout(() => {
        try {
            throw new Error('Error in callback');
        } catch (e) {
            callback(e, null);  // Error-first callback pattern
        }
    }, 100);
}

asyncOperation((error, result) => {
    if (error) {
        console.log('Error received:', error.message);
        return;
    }
    console.log('Result:', result);
});

Example: Global error handlers

// Browser: Global error handler
window.onerror = function(message, source, lineno, colno, error) {
    console.log('Global error caught:');
    console.log('Message:', message);
    console.log('Source:', source);
    console.log('Line:', lineno, 'Column:', colno);
    console.log('Error object:', error);
    
    // Return true to prevent default error handling
    return true;
};

// Browser: Unhandled promise rejections
window.onunhandledrejection = function(event) {
    console.log('Unhandled rejection:');
    console.log('Reason:', event.reason);
    console.log('Promise:', event.promise);
    
    // Prevent default handling
    event.preventDefault();
};

// Alternative with addEventListener
window.addEventListener('error', (event) => {
    console.log('Error event:', event.error);
});

window.addEventListener('unhandledrejection', (event) => {
    console.log('Unhandled rejection:', event.reason);
});

// Node.js: Global error handlers
process.on('uncaughtException', (error, origin) => {
    console.error('Uncaught exception:');
    console.error('Error:', error);
    console.error('Origin:', origin);
    
    // Clean up and exit
    // process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled rejection:');
    console.error('Reason:', reason);
    console.error('Promise:', promise);
});

// Example: Triggering global handlers
setTimeout(() => {
    throw new Error('Uncaught error in setTimeout');
}, 100);

Promise.reject(new Error('Unhandled promise rejection'));

// Centralized error logger
class ErrorLogger {
    static log(error, context = {}) {
        const errorInfo = {
            message: error.message,
            name: error.name,
            stack: error.stack,
            timestamp: new Date().toISOString(),
            context,
            url: typeof window !== 'undefined' ? window.location.href : null,
            userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null
        };
        
        console.error('Error logged:', errorInfo);
        
        // Send to monitoring service
        // sendToMonitoring(errorInfo);
    }
}

window.onerror = function(message, source, lineno, colno, error) {
    ErrorLogger.log(error || new Error(message), {
        source,
        lineno,
        colno
    });
    return true;
};

window.onunhandledrejection = function(event) {
    ErrorLogger.log(
        event.reason instanceof Error 
            ? event.reason 
            : new Error(String(event.reason)),
        {type: 'unhandled-rejection'}
    );
};

Example: Error recovery strategies

// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (attempt === maxRetries) {
                throw error;
            }
            
            const delay = baseDelay * Math.pow(2, attempt - 1);
            console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

// Usage
await retryWithBackoff(async () => {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('Fetch failed');
    return response.json();
});

// Fallback pattern
async function fetchWithFallback(primaryUrl, fallbackUrl) {
    try {
        return await fetch(primaryUrl);
    } catch (error) {
        console.log('Primary failed, using fallback');
        return await fetch(fallbackUrl);
    }
}

// Circuit breaker
class CircuitBreaker {
    constructor(threshold = 5, timeout = 60000) {
        this.failureCount = 0;
        this.threshold = threshold;
        this.timeout = timeout;
        this.state = 'CLOSED';  // CLOSED, OPEN, HALF_OPEN
        this.nextAttempt = Date.now();
    }
    
    async execute(fn) {
        if (this.state === 'OPEN') {
            if (Date.now() < this.nextAttempt) {
                throw new Error('Circuit breaker is OPEN');
            }
            this.state = 'HALF_OPEN';
        }
        
        try {
            const result = await fn();
            this.onSuccess();
            return result;
        } catch (error) {
            this.onFailure();
            throw error;
        }
    }
    
    onSuccess() {
        this.failureCount = 0;
        this.state = 'CLOSED';
    }
    
    onFailure() {
        this.failureCount++;
        if (this.failureCount >= this.threshold) {
            this.state = 'OPEN';
            this.nextAttempt = Date.now() + this.timeout;
            console.log(`Circuit breaker OPEN for ${this.timeout}ms`);
        }
    }
}

const breaker = new CircuitBreaker(3, 30000);

async function callExternalService() {
    return await breaker.execute(async () => {
        const response = await fetch('/external-api');
        if (!response.ok) throw new Error('Service unavailable');
        return response.json();
    });
}

// Graceful degradation
async function getUserData(userId) {
    try {
        // Try to fetch from API
        const response = await fetch(`/api/users/${userId}`);
        return await response.json();
    } catch (error) {
        console.warn('API unavailable, using cached data');
        
        try {
            // Fallback to cache
            return await getCachedUserData(userId);
        } catch (cacheError) {
            console.warn('Cache unavailable, using default data');
            
            // Final fallback to defaults
            return {
                id: userId,
                name: 'Guest User',
                avatar: '/default-avatar.png'
            };
        }
    }
}
Key Points: Exceptions propagate up synchronous call stack until caught. Promises propagate rejections through chain. Event handlers and timers don't propagate errors. Use window.onerror and onunhandledrejection for global handling. Implement retry with exponential backoff for transient failures. Circuit breaker pattern prevents cascading failures.

4. Console API (console.log, console.error, console.table, console.trace)

Console Logging Methods

Method Purpose Output Style
console.log() General logging Standard output
console.info() Informational messages Info icon (browser dependent)
console.warn() Warning messages Yellow warning icon
console.error() Error messages Red error icon with stack trace
console.debug() Debug messages (may be hidden) Debug level (filterable)

Console Formatting and Inspection

Method Syntax Description
console.dir() console.dir(object) Display object properties interactively
console.dirxml() console.dirxml(node) Display XML/HTML element tree
console.table() console.table(array) Display array/object as table
console.group() console.group(label) Start collapsible group
console.groupCollapsed() console.groupCollapsed(label) Start collapsed group
console.groupEnd() console.groupEnd() End current group

Console Utilities

Method Syntax Description
console.assert() console.assert(condition, message) Log error if condition false
console.count() console.count(label) Count calls with label
console.countReset() console.countReset(label) Reset counter for label
console.time() console.time(label) Start timer with label
console.timeLog() console.timeLog(label) Log current timer value
console.timeEnd() console.timeEnd(label) Stop timer and log duration
console.trace() console.trace(message) Log stack trace
console.clear() console.clear() Clear console

String Substitution Patterns

Pattern Type Example
%s String console.log('Hello %s', 'World')
%d or %i Integer console.log('Count: %d', 42)
%f Float console.log('Pi: %f', 3.14159)
%o Object console.log('Object: %o', obj)
%O Object (optimized) console.log('Object: %O', obj)
%c CSS styling console.log('%cStyled', 'color: red')

Example: Basic console methods

// Log levels
console.log('Standard log message');
console.info('Informational message');
console.warn('Warning message');
console.error('Error message');
console.debug('Debug message');

// Multiple arguments
console.log('Name:', 'John', 'Age:', 30, 'Active:', true);

// String substitution
console.log('Hello %s, you are %d years old', 'Alice', 25);
console.log('Pi is approximately %f', Math.PI);

// CSS styling
console.log(
    '%cStyled Text',
    'color: blue; font-size: 20px; font-weight: bold'
);

console.log(
    '%cError: %cSomething went wrong',
    'color: red; font-weight: bold',
    'color: orange'
);

// Object inspection
const user = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York',
        country: 'USA'
    }
};

console.log(user);        // Standard object display
console.dir(user);        // Interactive object properties
console.dir(user, {depth: null});  // Show all nested levels

// Table display
const users = [
    {id: 1, name: 'Alice', age: 25},
    {id: 2, name: 'Bob', age: 30},
    {id: 3, name: 'Charlie', age: 35}
];

console.table(users);
console.table(users, ['name', 'age']);  // Show specific columns

// DOM element
const element = document.getElementById('app');
console.log(element);      // DOM representation
console.dir(element);      // Object properties
console.dirxml(element);   // XML tree structure

Example: Console grouping and organization

// Simple grouping
console.group('User Details');
console.log('Name: John');
console.log('Age: 30');
console.log('Email: john@example.com');
console.groupEnd();

// Nested groups
console.group('Application Startup');
console.log('Initializing...');

console.group('Loading Modules');
console.log('Module A loaded');
console.log('Module B loaded');
console.log('Module C loaded');
console.groupEnd();

console.group('Connecting to Services');
console.log('Database connected');
console.log('API connected');
console.groupEnd();

console.log('Startup complete');
console.groupEnd();

// Collapsed groups
console.groupCollapsed('Debug Information');
console.log('This is collapsed by default');
console.log('User must expand to see');
console.groupEnd();

// Organizing API calls
async function fetchUserData(userId) {
    console.group(`Fetching user ${userId}`);
    console.time('fetch-duration');
    
    try {
        console.log('Sending request...');
        const response = await fetch(`/api/users/${userId}`);
        
        console.log('Response:', response.status);
        const data = await response.json();
        
        console.log('Data received:', data);
        console.timeEnd('fetch-duration');
        console.groupEnd();
        
        return data;
    } catch (error) {
        console.error('Fetch failed:', error);
        console.timeEnd('fetch-duration');
        console.groupEnd();
        throw error;
    }
}

// Function call tracing
function traceFunction(fn, name) {
    return function(...args) {
        console.group(`${name} called`);
        console.log('Arguments:', args);
        
        try {
            const result = fn(...args);
            console.log('Result:', result);
            console.groupEnd();
            return result;
        } catch (error) {
            console.error('Error:', error);
            console.groupEnd();
            throw error;
        }
    };
}

const add = traceFunction((a, b) => a + b, 'add');
add(2, 3);  // Logs group with args and result

Example: Console utilities

// Assertions
console.assert(1 === 1, 'This will not log');
console.assert(1 === 2, 'This WILL log as error');

function divide(a, b) {
    console.assert(b !== 0, 'Division by zero');
    return a / b;
}

divide(10, 0);  // Logs assertion error

// Counting
function processItem(item) {
    console.count('processItem');
    console.log('Processing:', item);
}

processItem('A');  // "processItem: 1"
processItem('B');  // "processItem: 2"
processItem('C');  // "processItem: 3"
console.countReset('processItem');
processItem('D');  // "processItem: 1" (reset)

// Counting with labels
function handleEvent(type) {
    console.count(type);
}

handleEvent('click');    // "click: 1"
handleEvent('scroll');   // "scroll: 1"
handleEvent('click');    // "click: 2"
handleEvent('scroll');   // "scroll: 2"

// Timing operations
console.time('operation');

// Simulate work
for (let i = 0; i < 1000000; i++) {
    // work
}

console.timeLog('operation');  // Intermediate timing
// More work
console.timeEnd('operation');  // Final timing and stop

// Timing async operations
async function measureAsync() {
    console.time('async-operation');
    
    await fetch('/api/data');
    console.timeLog('async-operation', 'After fetch');
    
    await processData();
    console.timeLog('async-operation', 'After processing');
    
    console.timeEnd('async-operation');
}

// Multiple timers
console.time('timer-1');
console.time('timer-2');

setTimeout(() => console.timeEnd('timer-1'), 100);
setTimeout(() => console.timeEnd('timer-2'), 200);

// Stack trace
function a() {
    b();
}

function b() {
    c();
}

function c() {
    console.trace('Called from c()');
}

a();  // Shows full call stack: c <- b <- a

// Performance monitoring
function measurePerformance(fn, name) {
    console.time(name);
    console.count(name);
    
    try {
        const result = fn();
        console.timeEnd(name);
        return result;
    } catch (error) {
        console.error(`${name} failed:`, error);
        console.timeEnd(name);
        throw error;
    }
}

measurePerformance(() => {
    // Some operation
    return Array(1000).fill(0).map((_, i) => i * 2);
}, 'array-operation');

Example: Custom logger implementation

// Production-ready logger
class Logger {
    constructor(options = {}) {
        this.level = options.level || 'info';
        this.prefix = options.prefix || '';
        this.levels = {
            debug: 0,
            info: 1,
            warn: 2,
            error: 3
        };
    }
    
    shouldLog(level) {
        return this.levels[level] >= this.levels[this.level];
    }
    
    format(level, ...args) {
        const timestamp = new Date().toISOString();
        const prefix = this.prefix ? `[${this.prefix}]` : '';
        return [`[${timestamp}]`, prefix, `[${level.toUpperCase()}]`, ...args];
    }
    
    debug(...args) {
        if (this.shouldLog('debug')) {
            console.debug(...this.format('debug', ...args));
        }
    }
    
    info(...args) {
        if (this.shouldLog('info')) {
            console.info(...this.format('info', ...args));
        }
    }
    
    warn(...args) {
        if (this.shouldLog('warn')) {
            console.warn(...this.format('warn', ...args));
        }
    }
    
    error(...args) {
        if (this.shouldLog('error')) {
            console.error(...this.format('error', ...args));
            console.trace();
        }
    }
    
    group(label) {
        console.group(...this.format('group', label));
    }
    
    groupEnd() {
        console.groupEnd();
    }
    
    time(label) {
        console.time(label);
    }
    
    timeEnd(label) {
        console.timeEnd(label);
    }
}

// Usage
const logger = new Logger({
    level: 'info',
    prefix: 'MyApp'
});

logger.debug('This will not show (below info level)');
logger.info('Application started');
logger.warn('Low memory');
logger.error('Fatal error occurred');

// Module-specific loggers
const apiLogger = new Logger({level: 'debug', prefix: 'API'});
const dbLogger = new Logger({level: 'info', prefix: 'DB'});

apiLogger.debug('Request received');
dbLogger.info('Query executed');

// Environment-aware logger
const isDevelopment = process.env.NODE_ENV === 'development';

const appLogger = new Logger({
    level: isDevelopment ? 'debug' : 'warn',
    prefix: 'App'
});

appLogger.debug('This shows only in development');
Key Points: Use console.log() for general, console.warn() for warnings, console.error() for errors. console.table() displays arrays beautifully. Group related logs with console.group(). console.time() measures performance. Use console.assert() for condition checks. %c enables CSS styling. Implement custom logger for production with log levels.

5. Debugging Techniques and Breakpoint Usage

Breakpoint Types

Type Usage When to Use
Line breakpoint Click line number in debugger Pause at specific line
Conditional breakpoint Right-click line, add condition Pause only when condition true
Logpoint Log without stopping execution Non-intrusive logging
debugger statement debugger; in code Programmatic breakpoint
DOM breakpoint Break on attribute/subtree changes Debug DOM mutations
XHR breakpoint Break on network requests Debug AJAX calls
Event listener breakpoint Break on event types Debug event handling
Exception breakpoint Break on caught/uncaught exceptions Debug error handling

Debugger Commands

Command Shortcut Action
Continue F8 / Cmd+\ Resume execution until next breakpoint
Step Over F10 / Cmd+' Execute current line, don't enter functions
Step Into F11 / Cmd+; Enter function calls
Step Out Shift+F11 / Shift+Cmd+; Exit current function
Step F9 Single-step through code (Node.js)

Debug Console Commands

Command Description Example
Evaluate expression Type any JavaScript expression user.name, 2 + 2
Modify variables Change values while paused count = 10
Call functions Execute functions processData()
$0 - $4 Recently inspected elements $0.classList.add('active')
$_ Last evaluated expression $_ * 2
$() querySelector shorthand $('#myId')
$$() querySelectorAll shorthand $$('.myClass')

Debugging Best Practices

Practice Description Benefit
Source maps Map minified code to original Debug production builds
Conditional breakpoints Break only on specific conditions Avoid breaking on every iteration
Watch expressions Monitor variable values Track state changes
Call stack inspection View function call hierarchy Understand execution flow
Blackbox scripts Skip library code Focus on application code

Example: Debugger statement and breakpoints

// debugger statement
function calculateTotal(items) {
    let total = 0;
    
    for (const item of items) {
        debugger;  // Execution pauses here when DevTools open
        total += item.price * item.quantity;
    }
    
    return total;
}

// Conditional debugger
function processUsers(users) {
    for (const user of users) {
        // Only break for specific user
        if (user.id === 123) {
            debugger;
        }
        
        processUser(user);
    }
}

// Debug with assertions
function divide(a, b) {
    console.assert(typeof a === 'number', 'a must be number');
    console.assert(typeof b === 'number', 'b must be number');
    console.assert(b !== 0, 'Division by zero');
    
    return a / b;
}

// Debug wrapper
function debug(fn, name) {
    return function(...args) {
        console.group(`Debug: ${name}`);
        console.log('Arguments:', args);
        console.time(name);
        
        debugger;  // Pause before execution
        
        try {
            const result = fn(...args);
            console.log('Result:', result);
            console.timeEnd(name);
            console.groupEnd();
            return result;
        } catch (error) {
            console.error('Error:', error);
            console.timeEnd(name);
            console.groupEnd();
            throw error;
        }
    };
}

const debugAdd = debug((a, b) => a + b, 'add');
debugAdd(2, 3);

// Breakpoint on error
function riskyOperation() {
    try {
        // risky code
        throw new Error('Something went wrong');
    } catch (error) {
        debugger;  // Pause to inspect error
        console.error('Error caught:', error);
    }
}

Example: Debugging async code

// Debug promise chain
fetch('/api/users')
    .then(response => {
        debugger;  // Inspect response
        return response.json();
    })
    .then(data => {
        debugger;  // Inspect parsed data
        console.log('Users:', data);
    })
    .catch(error => {
        debugger;  // Inspect error
        console.error('Fetch failed:', error);
    });

// Debug async/await
async function fetchUserData(userId) {
    try {
        console.log('Fetching user:', userId);
        
        debugger;  // Before fetch
        const response = await fetch(`/api/users/${userId}`);
        
        debugger;  // After fetch, inspect response
        const data = await response.json();
        
        debugger;  // After parsing, inspect data
        return data;
    } catch (error) {
        debugger;  // On error
        console.error('Error:', error);
        throw error;
    }
}

// Debug with detailed logging
async function debugAsyncOperation(operation, name) {
    console.group(`Async: ${name}`);
    console.time(name);
    
    try {
        console.log('Starting...');
        const result = await operation();
        
        console.log('Completed:', result);
        console.timeEnd(name);
        console.groupEnd();
        
        return result;
    } catch (error) {
        console.error('Failed:', error);
        console.trace();
        console.timeEnd(name);
        console.groupEnd();
        
        throw error;
    }
}

// Usage
await debugAsyncOperation(
    () => fetch('/api/data').then(r => r.json()),
    'fetch-data'
);

// Debug race conditions
let requestCount = 0;

async function debugRaceCondition() {
    const id = ++requestCount;
    
    console.log(`Request ${id} started`);
    debugger;
    
    await new Promise(r => setTimeout(r, Math.random() * 1000));
    
    console.log(`Request ${id} completed`);
    debugger;
}

// Multiple concurrent calls
Promise.all([
    debugRaceCondition(),
    debugRaceCondition(),
    debugRaceCondition()
]);

Example: Advanced debugging techniques

// Performance profiling
function profileFunction(fn, iterations = 1000) {
    console.time('profile');
    
    for (let i = 0; i < iterations; i++) {
        fn();
    }
    
    console.timeEnd('profile');
}

profileFunction(() => {
    // Code to profile
    const arr = Array(100).fill(0).map((_, i) => i * 2);
});

// Memory usage tracking (Chrome)
function checkMemory() {
    if (performance.memory) {
        console.group('Memory Usage');
        console.log('Used:', 
            Math.round(performance.memory.usedJSHeapSize / 1048576), 'MB');
        console.log('Total:', 
            Math.round(performance.memory.totalJSHeapSize / 1048576), 'MB');
        console.log('Limit:', 
            Math.round(performance.memory.jsHeapSizeLimit / 1048576), 'MB');
        console.groupEnd();
    }
}

// Before operation
checkMemory();

// Do memory-intensive work
const largeArray = Array(1000000).fill({data: 'test'});

// After operation
checkMemory();

// Stack trace analysis
function captureCallers() {
    const stack = new Error().stack;
    const lines = stack.split('\n');
    
    console.group('Call Stack');
    lines.slice(2).forEach(line => {  // Skip Error and this function
        console.log(line.trim());
    });
    console.groupEnd();
}

function a() { b(); }
function b() { c(); }
function c() { captureCallers(); }

a();

// Debugging higher-order functions
function debugHOF(fn, name) {
    return function(...args) {
        console.group(`HOF: ${name}`);
        console.log('Input:', args);
        
        const result = fn(...args);
        
        console.log('Output:', result);
        console.groupEnd();
        
        return result;
    };
}

const numbers = [1, 2, 3, 4, 5];

const debugMap = debugHOF(x => x * 2, 'double');
const debugFilter = debugHOF(x => x > 2, 'greaterThan2');
const debugReduce = debugHOF((a, b) => a + b, 'sum');

const doubled = numbers.map(debugMap);
const filtered = numbers.filter(debugFilter);
const sum = numbers.reduce(debugReduce, 0);

// Debug event listeners
function debugEventListener(element, event, handler) {
    element.addEventListener(event, function(...args) {
        console.group(`Event: ${event}`);
        console.log('Target:', args[0].target);
        console.log('Event:', args[0]);
        console.trace('Call stack');
        
        const result = handler.apply(this, args);
        
        console.groupEnd();
        return result;
    });
}

const button = document.getElementById('myButton');
debugEventListener(button, 'click', (e) => {
    console.log('Button clicked');
});
Key Points: Use debugger statement for programmatic breakpoints. Conditional breakpoints prevent breaking on every iteration. Step Over skips function calls, Step Into enters them. Inspect call stack to understand execution flow. Use watch expressions to monitor variables. Enable source maps for debugging transpiled code.

6. Error Monitoring and Production Debugging

Error Monitoring Services

Service Features Use Case
Sentry Error tracking, performance, releases Full-featured error monitoring
Rollbar Real-time error tracking Error aggregation and alerts
Bugsnag Error monitoring, stability score Mobile and web apps
LogRocket Session replay, error tracking User session debugging
Datadog APM, logging, error tracking Full observability platform
New Relic APM, browser monitoring Application performance

Error Context Data

Category Data Purpose
User Info ID, email, username Identify affected users
Environment Browser, OS, device Reproduce environment
Application Version, build, release Track regressions
Request URL, method, headers Understand context
Breadcrumbs User actions, navigation Trace user journey
Custom Tags Feature flags, experiments Segment and filter errors

Production Debugging Strategies

Strategy Implementation Benefit
Source maps Upload to error monitoring service Debug minified code
Structured logging JSON format with context Searchable, parseable logs
Correlation IDs Track requests across services Distributed tracing
Feature flags Enable debug mode for specific users Production debugging
Error sampling Rate limit error reports Reduce noise, cost
Release tracking Tag errors with version Identify regressions

Example: Error monitoring setup

// Sentry integration example
import * as Sentry from '@sentry/browser';

Sentry.init({
    dsn: 'YOUR_SENTRY_DSN',
    environment: process.env.NODE_ENV,
    release: 'my-app@1.0.0',
    
    // Performance monitoring
    tracesSampleRate: 0.1,
    
    // Error filtering
    beforeSend(event, hint) {
        // Filter out certain errors
        if (event.exception) {
            const error = hint.originalException;
            
            // Ignore network errors
            if (error.message.includes('NetworkError')) {
                return null;
            }
            
            // Ignore specific status codes
            if (error.statusCode === 404) {
                return null;
            }
        }
        
        return event;
    },
    
    // PII scrubbing
    beforeBreadcrumb(breadcrumb) {
        if (breadcrumb.category === 'xhr') {
            // Remove sensitive data from URLs
            breadcrumb.data.url = breadcrumb.data.url.replace(
                /token=([^&]+)/,
                'token=[REDACTED]'
            );
        }
        return breadcrumb;
    }
});

// Set user context
Sentry.setUser({
    id: user.id,
    email: user.email,
    username: user.username
});

// Set custom tags
Sentry.setTag('feature_flag_new_ui', 'enabled');
Sentry.setTag('subscription_tier', 'premium');

// Set custom context
Sentry.setContext('app', {
    build: '12345',
    commit: 'abc123'
});

// Manual error reporting
try {
    riskyOperation();
} catch (error) {
    Sentry.captureException(error, {
        level: 'error',
        tags: {
            operation: 'riskyOperation'
        },
        contexts: {
            operation: {
                input: operationInput,
                attemptNumber: retryCount
            }
        }
    });
}

// Breadcrumbs for debugging
Sentry.addBreadcrumb({
    category: 'auth',
    message: 'User logged in',
    level: 'info'
});

Sentry.addBreadcrumb({
    category: 'navigation',
    message: 'Navigated to /dashboard',
    level: 'info'
});

// Capture message (non-error)
Sentry.captureMessage('Unusual condition detected', 'warning');

Example: Production logging system

// Structured logger for production
class ProductionLogger {
    constructor(config = {}) {
        this.serviceName = config.serviceName || 'app';
        this.environment = config.environment || 'production';
        this.version = config.version || '1.0.0';
    }
    
    createLogEntry(level, message, context = {}) {
        return {
            timestamp: new Date().toISOString(),
            level,
            message,
            service: this.serviceName,
            environment: this.environment,
            version: this.version,
            correlation_id: context.correlationId || this.generateCorrelationId(),
            user_id: context.userId,
            session_id: context.sessionId,
            url: typeof window !== 'undefined' ? window.location.href : undefined,
            user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
            ...context.extra
        };
    }
    
    generateCorrelationId() {
        return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
    
    log(level, message, context) {
        const entry = this.createLogEntry(level, message, context);
        
        // Send to logging service
        this.send(entry);
        
        // Also log to console in development
        if (this.environment === 'development') {
            console[level](message, entry);
        }
    }
    
    async send(entry) {
        try {
            await fetch('/api/logs', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(entry)
            });
        } catch (error) {
            // Fallback: log to console
            console.error('Failed to send log:', error);
        }
    }
    
    info(message, context) {
        this.log('info', message, context);
    }
    
    warn(message, context) {
        this.log('warn', message, context);
    }
    
    error(message, error, context = {}) {
        this.log('error', message, {
            ...context,
            extra: {
                ...context.extra,
                error_name: error.name,
                error_message: error.message,
                error_stack: error.stack
            }
        });
    }
}

const logger = new ProductionLogger({
    serviceName: 'frontend-app',
    environment: process.env.NODE_ENV,
    version: process.env.APP_VERSION
});

// Usage
logger.info('User logged in', {
    userId: '123',
    extra: {
        login_method: 'oauth'
    }
});

try {
    await fetchData();
} catch (error) {
    logger.error('Failed to fetch data', error, {
        userId: currentUser.id,
        extra: {
            endpoint: '/api/data',
            retry_count: 3
        }
    });
}

Example: Error sampling and rate limiting

// Error sampling to reduce noise
class ErrorSampler {
    constructor(sampleRate = 0.1) {
        this.sampleRate = sampleRate;
        this.errorCounts = new Map();
        this.resetInterval = 60000;  // 1 minute
        
        setInterval(() => this.errorCounts.clear(), this.resetInterval);
    }
    
    shouldReport(error) {
        const errorKey = `${error.name}:${error.message}`;
        const count = this.errorCounts.get(errorKey) || 0;
        
        // Always report first occurrence
        if (count === 0) {
            this.errorCounts.set(errorKey, 1);
            return true;
        }
        
        // Sample subsequent occurrences
        this.errorCounts.set(errorKey, count + 1);
        return Math.random() < this.sampleRate;
    }
    
    report(error, context = {}) {
        if (this.shouldReport(error)) {
            // Send to monitoring service
            errorMonitoring.captureException(error, {
                ...context,
                extra: {
                    ...context.extra,
                    error_count: this.errorCounts.get(
                        `${error.name}:${error.message}`
                    )
                }
            });
        }
    }
}

const sampler = new ErrorSampler(0.1);  // 10% sample rate

// Use in error handler
window.onerror = function(message, source, lineno, colno, error) {
    sampler.report(error || new Error(message), {
        extra: {source, lineno, colno}
    });
};

// Rate limiter for specific error types
class ErrorRateLimiter {
    constructor(maxErrors = 10, windowMs = 60000) {
        this.maxErrors = maxErrors;
        this.windowMs = windowMs;
        this.errors = new Map();
    }
    
    canReport(errorType) {
        const now = Date.now();
        const errors = this.errors.get(errorType) || [];
        
        // Remove old errors outside window
        const recentErrors = errors.filter(
            time => now - time < this.windowMs
        );
        
        if (recentErrors.length >= this.maxErrors) {
            return false;
        }
        
        recentErrors.push(now);
        this.errors.set(errorType, recentErrors);
        
        return true;
    }
}

const rateLimiter = new ErrorRateLimiter(10, 60000);

function reportError(error) {
    if (rateLimiter.canReport(error.name)) {
        errorMonitoring.captureException(error);
    } else {
        console.warn(`Rate limit exceeded for ${error.name}`);
    }
}

Example: Release tracking and version management

// Release tracking
class ReleaseTracker {
    constructor(config) {
        this.version = config.version;
        this.buildId = config.buildId;
        this.gitCommit = config.gitCommit;
        this.environment = config.environment;
        
        this.initMonitoring();
    }
    
    initMonitoring() {
        // Configure error monitoring with release info
        errorMonitoring.setRelease({
            version: this.version,
            buildId: this.buildId,
            commit: this.gitCommit
        });
        
        // Track release deployment
        this.trackDeployment();
    }
    
    async trackDeployment() {
        try {
            await fetch('/api/deployments', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    version: this.version,
                    buildId: this.buildId,
                    commit: this.gitCommit,
                    environment: this.environment,
                    timestamp: new Date().toISOString()
                })
            });
        } catch (error) {
            console.error('Failed to track deployment:', error);
        }
    }
    
    async compareWithPrevious() {
        // Fetch error rates from monitoring service
        const currentErrors = await this.getErrorRate(this.version);
        const previousErrors = await this.getErrorRate(this.previousVersion);
        
        const increase = currentErrors - previousErrors;
        const percentIncrease = (increase / previousErrors) * 100;
        
        if (percentIncrease > 50) {
            this.alertHighErrorRate(percentIncrease);
        }
    }
}

// Initialize on app start
const tracker = new ReleaseTracker({
    version: process.env.APP_VERSION,
    buildId: process.env.BUILD_ID,
    gitCommit: process.env.GIT_COMMIT,
    environment: process.env.NODE_ENV
});

// Feature flag debugging
class FeatureFlagDebugger {
    constructor() {
        this.flags = new Map();
        this.debugEnabled = false;
    }
    
    enableDebug(userId) {
        // Enable verbose logging for specific user
        if (this.isDebugUser(userId)) {
            this.debugEnabled = true;
            console.log('Debug mode enabled for user:', userId);
        }
    }
    
    isDebugUser(userId) {
        // Check if user is in debug list
        return ['debug-user-1', 'admin'].includes(userId);
    }
    
    log(...args) {
        if (this.debugEnabled) {
            console.log('[DEBUG]', ...args);
        }
    }
    
    setFlag(name, value) {
        this.flags.set(name, value);
        this.log(`Feature flag set: ${name} = ${value}`);
    }
    
    getFlag(name) {
        const value = this.flags.get(name);
        this.log(`Feature flag get: ${name} = ${value}`);
        return value;
    }
}

const featureFlags = new FeatureFlagDebugger();

// Enable debug mode for specific user
if (currentUser.id === 'debug-user-1') {
    featureFlags.enableDebug(currentUser.id);
}
Best Practices: Use error monitoring service like Sentry or Rollbar. Include context: user, environment, version. Implement error sampling to reduce noise. Upload source maps for debugging minified code. Track releases to identify regressions. Use structured logging with correlation IDs. Implement rate limiting for error reporting. Enable debug mode for specific users in production.

Section 19 Summary: Error Handling and Debugging Techniques

  • Error Types: Error, TypeError, ReferenceError, RangeError, SyntaxError, URIError, AggregateError
  • Error Properties: name, message, stack, cause (ES2022)
  • Custom Errors: Extend Error class, add domain-specific properties
  • Exception Propagation: Sync code up call stack, promises through chain, async/await
  • Global Handlers: window.onerror, onunhandledrejection, process uncaughtException
  • Error Recovery: Retry with backoff, fallback, circuit breaker, graceful degradation
  • Console API: log, warn, error, table, group, time, trace, assert
  • Debugging: debugger statement, breakpoints, step commands, watch expressions
  • Production Monitoring: Sentry, Rollbar, structured logging, source maps
  • Best Practices: Error sampling, rate limiting, release tracking, correlation IDs