Skip to solution
mediumLow-Level Design

How do you handle uncaught exceptions and unhandled promise rejections in Node.js?

423 views
01

Understand the problem

Question presented to candidate: "A Promise rejects and nothing ever calls .catch() on it. Does that crash your Node process today, and has that behavior always been true?"

What a strong answer should cover:

  • process.on("uncaughtException", ...) and process.on("unhandledRejection", ...) are two distinct events, for two distinct failure shapes: a genuine synchronous throw that propagates all the way up uncaught, versus a rejected Promise that no code ever attached a .catch()/await-try/catch to.
  • 📌 Verified, version-relevant behavior: with no handler registered at all, an unhandled Promise rejection crashes the process immediately, with a nonzero exit code — this is Node's modern default (since Node 15), not merely a warning as in some older versions; a precise answer does not assume the older, warn-only behavior without checking the actual Node version in use.
  • Both events are correctly understood as a last-resort safety net, not a general-purpose error-handling mechanism — the right place to handle an error is as close as possible to where it actually occurs (a try/catch, a .catch(), a centralized Express error middleware), not by relying on a process-wide handler to catch everything after the fact.
  • The standard, correct pattern for these handlers: log the error with full context, then deliberately exit the process (process.exit(1), or let the crash proceed) so a supervisor (a container orchestrator, pm2, systemd) restarts it fresh — connecting directly to the operational-vs-programmer-errors question, since reaching this handler at all generally signals an unknown, untrusted program state.
  • A precise answer names the real, common cause of unhandledRejection specifically: an async function called without awaiting it and with no .catch() attached to the resulting Promise — the call still runs, but nothing observes a later rejection.
  • process.on("uncaughtExceptionMonitor", ...) is a related, less commonly known event — it fires alongside uncaughtException without suppressing Node's own default handling, useful specifically for observability/logging without altering the crash behavior itself.

Clarifying questions expected:

  • "Which Node version — does the codebase rely on older warn-only unhandledRejection behavior, or the current terminate-by-default behavior?" — genuinely different, and worth confirming rather than assuming.
  • "Is the goal catching these as a last resort, or actually preventing them by fixing the missing .catch()/try/catch at the source?" — the latter is almost always the better fix.

Code / implementation expected: Yes — demonstrating both events firing distinctly, and the real crash-with-no-handler default behavior, is the concrete, convincing part of the answer.

errorsprocessbest-practices
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js interviews — assumes basic Promise/async familiarity. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. Both the handled and the genuinely unhandled (crashing) cases b

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

uncaughtException and unhandledRejection firing distinctly, and the real crash-with-no-handler default behavior
// With handlers attached — both fire for their own distinct failure shape:
process.on("uncaughtException", (err, origin) => {
  console.log("uncaughtException:", err.message, "| origin:", origin);
});
process.on("unhandledRejection", (reason) => {
  console.log("unhandledRejection:", reason.message);
});

Promise.reject(new Error("a rejected promise nobody caught"));
setTimeout(() => { throw new Error("a synchronous throw nobody caught"); }, 50);
// unhandledRejection: a rejected promise nobody caught
// uncaughtException: a synchronous throw nobody caught | origin: uncaughtException

// --- Separately, with NO handler at all: ---
// Promise.reject(new Error("nobody catches this"));
// $ node script.js; echo "exit code: $?"
// Error: nobody catches this
//     at ...
// exit code: 1   <- crashed immediately, the modern default

// The most common real cause:
async function doWork() { throw new Error("boom"); }
doWork(); // called without await, no .catch() — genuinely unhandled
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 92 of 152 decoded in the Node.js track. One more won't hurt.

Back to track