How does error propagation work in JavaScript?
JavaScriptThe short answer
When an error is thrown, JavaScript stops executing the current function and travels back up the call stack looking for a try...catch block. If it finds one, the catch block handles the error. If it reaches the top of the call stack without finding a handler, the error becomes an unhandled exception and the program crashes (or the browser logs it to the console).
How errors propagate up the call stack
function c() { throw new Error('Something broke');}function b() { c(); // error thrown here, not caught}function a() { b(); // error passes through here too}try { a();} catch (error) { console.log('Caught:', error.message); // "Caught: Something broke"}The error was thrown in c, passed through b (no catch), passed through a (no catch), and was finally caught in the outer try...catch. Each function was removed from the call stack as the error "bubbled up."
Catching at different levels
You can catch errors at any level in the call stack:
function riskyOperation() { throw new Error('Disk full');}function saveFile() { try { riskyOperation(); } catch (error) { // Handle at this level console.log('Save failed:', error.message); // Optionally re-throw if the caller should also know throw new Error('Could not save file'); }}try { saveFile();} catch (error) { console.log('Operation failed:', error.message);}saveFile catches the error, handles it (logs it), and throws a new, higher-level error. The outer code catches that new error.
With async/await
Errors in async functions propagate through rejected promises:
async function fetchData() { const response = await fetch('/api/data'); if (!response.ok) { throw new Error('Fetch failed'); } return response.json();}async function loadDashboard() { const data = await fetchData(); // error propagates here renderDashboard(data);}// Catch at the top levelloadDashboard().catch((error) => { console.log('Dashboard failed:', error.message);});If fetchData throws, loadDashboard does not catch it, so it becomes a rejected promise. The .catch() at the end handles it.
Unhandled errors
If no try...catch catches the error:
- In the browser — an
errorevent fires onwindow, and the error appears in the console - In Node.js — an
uncaughtExceptionevent fires, and the process may crash
// Global error handler in the browserwindow.addEventListener('error', (event) => { console.log('Unhandled error:', event.message);});// For unhandled promise rejectionswindow.addEventListener('unhandledrejection', (event) => { console.log('Unhandled rejection:', event.reason);});Best practices
- Catch errors at the right level — not every function needs a try...catch. Catch where you can meaningfully handle the error.
- Do not swallow errors — if you catch an error but cannot handle it, re-throw it so the caller knows something went wrong.
- Use specific error types — catch specific errors when possible instead of catching everything.
Interview Tip
Walk through the call stack example step by step — show how the error travels up from c through b through a until it is caught. Mention that async errors propagate through rejected promises. Knowing about unhandledrejection shows you think about production error handling.
Why interviewers ask this
This question tests if you understand JavaScript's error handling model. Interviewers want to see if you know how errors move through the call stack, where to catch them, and what happens when they are not caught. It is fundamental knowledge for writing robust applications.