Error Handling

10 questions found

How does a try/catch block work?

Beginner
Code inside try runs normally; if it throws an error, execution immediately jumps to the catch block instead of crashing the whole program, letting you handle the error gracefully.
try {
  JSON.parse('not valid json');
} catch (err) {
  console.log('Failed to parse:', err.message);
}
Real-world example Safely parsing user-provided JSON input without crashing the app on malformed data.

Common follow-ups: What runs if there's no error at all — does catch still execute?

JSON & Data Serialization

What does the finally block do?

Beginner
finally always runs after try/catch, whether or not an error was thrown or caught, and even if the try block returns early — making it ideal for cleanup code that must always execute.
function readFile() {
  openFile();
  try {
    return process();
  } finally {
    closeFile(); // always runs
  }
}
Real-world example Ensuring a loading spinner is hidden whether an API call succeeds or fails.

Common follow-ups: Does a return inside finally override a return from try?

Async Iterators & Streams

What's the difference between a built-in Error and throwing a plain string or object?

Beginner
new Error('message') creates an object with a stack trace and standard .message/.name properties, which DevTools and error-tracking tools understand. Throwing a plain string or object loses the stack trace, making debugging much harder.
throw new Error('Invalid input');    // has a stack trace
throw 'Invalid input';               // no stack trace, harder to debug
Real-world example Always throwing proper Error instances (or subclasses) so production error-tracking tools like Sentry capture full context.

Common follow-ups: How do you create a custom error type by extending Error?

Classes & Class Syntax

How do you create a custom error class?

Intermediate
Extend the built-in Error class, call super(message) in the constructor to set up the message and stack trace, then add any extra properties specific to your error type.
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}
throw new ValidationError('Required', 'email');
Real-world example Distinguishing a ValidationError from a NetworkError in a catch block to show different UI messages.

Common follow-ups: Why do you need to explicitly set this.name in a custom error subclass?

Classes & Class Syntax

How do you catch errors from a Promise chain versus async/await?

Intermediate
With .then()/.catch() chains, attach a .catch() at the end to handle rejections from any prior step. With async/await, wrap the awaited calls in a try/catch block — both approaches ultimately catch the same underlying rejected Promise.
// Promise chain
fetchData().then(process).catch(err => console.error(err));

// async/await
try {
  const data = await fetchData();
  process(data);
} catch (err) {
  console.error(err);
}
Real-world example Handling a failed API call gracefully instead of an unhandled promise rejection crashing the app.

Common follow-ups: What happens to errors thrown inside a .then() callback — are they caught by a later .catch()?

Promises & async/await

What is error propagation, and how does an uncaught error in a nested function behave?

Intermediate
If a function doesn't catch an error itself, it automatically propagates up the call stack to the nearest enclosing try/catch (or crashes the program/rejects the promise if none exists) — you don't need to manually re-throw at every level.
function inner() { throw new Error('boom'); }
function outer() { inner(); } // doesn't need its own try/catch
try {
  outer();
} catch (err) {
  console.log('Caught:', err.message); // 'Caught: boom'
}
Real-world example Letting a deeply nested validation function throw, and catching it once at the top-level request handler.

Common follow-ups: What is the 'error boundary' pattern, and how does it relate to this in UI frameworks?

Design Patterns in JavaScript

What is the Error cause option and what problem does it solve?

Advanced
The { cause } option on the Error constructor lets you wrap a lower-level error inside a higher-level, more meaningful one while preserving the original error for debugging — avoiding the common trade-off of losing context vs. re-throwing raw low-level errors.
try {
  await fetchUser(id);
} catch (err) {
  throw new Error('Failed to load user profile', { cause: err });
}
Real-world example Wrapping a low-level 'fetch failed' network error with a higher-level, user-facing 'Failed to load profile' error while keeping the original for logs.

Common follow-ups: How do you access the original error from the cause property later?

Networking: Fetch XHR WebSockets & CORS

How do global error handlers like window.onerror and unhandledrejection work?

Advanced
window.addEventListener('error', ...) catches uncaught synchronous exceptions bubbling up to the top; window.addEventListener('unhandledrejection', ...) catches Promise rejections that were never handled by a .catch(). Together they form a last-resort safety net for logging errors that slipped past local handling.
window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled rejection:', event.reason);
  event.preventDefault(); // suppress default browser logging
});
Real-world example Reporting any error that slips through the app's normal handling to a monitoring service like Sentry.

Common follow-ups: Should you rely on these global handlers instead of local try/catch blocks?

Promises & async/await

Why can errors thrown inside a setTimeout callback NOT be caught by a surrounding try/catch?

Advanced
The try/catch block finishes executing (and its stack frame is gone) long before the setTimeout callback actually runs on a later turn of the event loop — by the time the error is thrown, there's no enclosing try/catch left to catch it.
try {
  setTimeout(() => { throw new Error('too late!'); }, 100);
} catch (err) {
  // never runs — the throw happens asynchronously, after this try/catch exits
}
Real-world example A confusing bug where a developer expects a try/catch to catch an error from an async callback, but it silently escapes to the console instead.

Common follow-ups: Where should the try/catch actually go to catch an error thrown inside a setTimeout callback?

Event Loop & Concurrency

How do you implement a retry-with-backoff pattern around a function that might throw?

Advanced
Wrap the call in a loop with a try/catch; on failure, wait an increasing delay (e.g. exponential backoff) before retrying, up to a maximum attempt count, then re-throw the final error if all attempts fail.
async function retry(fn, attempts = 3, delay = 500) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts - 1) throw err;
      await new Promise(r => setTimeout(r, delay * 2 ** i));
    }
  }
}
Real-world example Retrying a flaky network request up to three times with increasing delays before giving up and showing an error.

Common follow-ups: How would you avoid retrying on errors that are clearly not transient, like a 404?

Promises & async/await