Interview Q&A / Node.js

IntermediateIn-depth3 min readUpdated 2 Oct 2026

How do you handle errors in asynchronous Node.js code, and what is an unhandled promise rejection?

SHORT ANSWER

Use try/catch around await, .catch() on promise chains, and the error-first argument in callbacks. An unhandled rejection is a promise that fails with no handler attached; since Node 15 it crashes the process by default, so every promise needs a catch somewhere.

Explain it simply

When you place a Swiggy order, many things can fail later: the restaurant may cancel, the payment may bounce. If nobody is assigned to handle those failures, the order just breaks silently and you never hear back. In async code, errors also happen later, after the function that started the work has already moved on. You need to say in advance who will handle the failure.

A plain try/catch only catches errors thrown while it is running. It cannot catch an error that happens later inside a callback, unless you are using await, which pauses the function so the error comes back into the try block.

Example

js
const fs = require("node:fs/promises");

// 1. async/await with try/catch
async function loadConfig() {
  try {
    const text = await fs.readFile("./config.json", "utf8");
    return JSON.parse(text);
  } catch (err) {
    console.error("Could not load config:", err.message);
    return {}; // fall back to defaults
  }
}

// 2. Promise chain with .catch()
fs.readFile("./config.json", "utf8")
  .then(JSON.parse)
  .catch((err) => console.error("Failed:", err.message));

// 3. Old callback style: error is the first argument
require("node:fs").readFile("./config.json", "utf8", (err, text) => {
  if (err) return console.error(err.message);
  console.log(text);
});

This is the bug that causes an unhandled rejection:

js
async function saveOrder() {
  throw new Error("DB down");
}

saveOrder(); // no await, no .catch() -> unhandled rejection, process exits

How it works

  • A rejected promise looks for a .catch() or a try/catch around an await. If none is attached by the time the microtask queue is processed, Node emits the unhandledRejection event.
  • Since Node 15, the default mode is --unhandled-rejections=throw: the rejection is raised as an uncaught exception and the process exits with a non-zero code.
  • process.on("uncaughtException") and process.on("unhandledRejection") let you log the error before shutting down. They are a last safety net, not a normal way to handle errors.

Real-world use

In an API, wrap each request so failures become a proper HTTP error response (for example a central Express error handler). For background jobs, catch errors per job so one failed SMS does not stop the whole batch. Use a process manager such as PM2, systemd or Kubernetes to restart the app if it does crash.

Common mistakes

  • Calling an async function without await or .catch() ("fire and forget").
  • Using array.forEach(async ...); the errors and the completion are not awaited. Use for...of with await, or Promise.all.
  • Wrapping a callback-based call in try/catch and expecting it to catch the async error.
  • Using uncaughtException to keep the server running. After an unknown error the app state may be broken; log it and restart.

What to say in the interview

"For async code I use try/catch around await, .catch() on promise chains, and check the error argument in callback APIs. An unhandled promise rejection is a promise that rejects with nothing handling it. In modern Node that crashes the process by default, which is good because silent failures are worse. I make sure every promise is awaited or has a catch, I use Promise.allSettled when some failures are acceptable, and I add unhandledRejection and uncaughtException listeners only to log and shut down cleanly before a process manager restarts the app."

Follow-up questions

  • Why does try/catch not catch an error thrown inside a setTimeout callback?
  • What is the difference between Promise.all and Promise.allSettled for error handling?
  • Should you keep the process running after an uncaughtException?

Go deeper

Read these once the basics above feel easy.

Custom error classes (class NotFoundError extends Error) let a central handler map errors to status codes. Since ES2022, new Error("Payment failed", { cause: err }) keeps the original error attached, which makes logs much easier to follow across layers. For graceful shutdown, stop accepting new connections with server.close(), finish in-flight requests, close database pools, and then exit.

#nodejs#error-handling#promises#async

Related questions

Practise more Node.js questions

Test your recall with short answers, then return to the detailed explanations when you need context.

Open Node.js quick answers →

Get new questions like this by email

One short email when something new and useful lands. No spam, unsubscribe in one tap.

By subscribing you agree to the Privacy Policy.