Under The Hood
backend nodejs Node.js 20+ (unhandled rejection handling)

unhandledRejection vs uncaughtException: Event Loop Termination

Last updated
Prerequisites:
Node.js promises and async/await
Understanding of the event loop and process lifecycle
  • nodejs
  • unhandled-rejection
  • uncaught-exception
  • errors
  • process
  • exit

Read at your depth

The practical view

uncaughtException fires when an exception is thrown in synchronous code and never caught: process.on('uncaughtException', (err) => { ... }). unhandledRejection fires when a Promise rejects and no .catch() or try/catch (via await) handles it: process.on('unhandledRejection', (reason, promise) => { ... }). Since Node 15, an unhandled rejection CRASHES the process by default (before, it only warned). The recommended pattern: crash and restart (let the process manager restart you), or at most log and gracefully shut down — do NOT try to continue running after an uncaught exception because the state may be corrupt.

The same idea in other frameworks

react equivalent
N/A — Node.js runtime concern.

Legacy vs modern

Silently ignoring a rejected promise vs handling it

An unhandled rejection crashes the process by default; attaching a catch (or handling the event) keeps the app from terminating unexpectedly.

before → after
Forgotten rejection handler
async function run() {
  const data = await fetchData(); // ❌ OLD: if fetchData rejects and no catch...
  use(data);
}
Explicit rejection handling
try {
  const data = await fetchData(); // ✅ NEW: handled
  use(data);
} catch (err) {
  log.error(err); // ✅ NEW: rejections handled at the source
}

Interview gotchas

Context

This tests the error-model distinction — interviewers want the sync-vs-async error paths and the state-corruption rationale for the 'log and exit' guidance.

The mechanical answer

uncaughtException is a synchronous exception that escaped the call stack — the stack unwound mid-operation, so locks, invariants, transaction state, or middleware may be left inconsistent. Continuing execution from that point is unsupported and can corrupt data or hang, so the guidance is to log the error and exit (letting the process manager restart). unhandledRejection is a rejected Promise with no handler — the rejection was detected asynchronously (at the microtask checkpoint). The app's synchronous state may be perfectly fine, but the code that produced the rejection intended to handle it, so continuing means that logic silently failed. Node's default (v15+) is to terminate on unhandled rejections, matching the uncaught-exception behavior. The shared rule: if you handle either event, you should still exit rather than resume, because you cannot know how far the failure propagated.

Trap

The naive answer is 'just add a global handler and continue'. Resuming after uncaughtException is explicitly unsupported (undefined behavior); after unhandledRejection, the rejecting operation failed silently. Another trap: thinking catching an await inside one function protects the whole app — a promise that escapes (e.g. returned from an event handler nobody awaits) can still reject unhandled. Candidates who describe the state-corruption rationale show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.