Error Handling in Node.js


Error Handling in Node.js Interview with follow-up questions

1. What is error handling in Node.js and why is it important?

Error handling is detecting, propagating, and responding to failures — bad input, failed I/O, rejected promises, thrown exceptions — so the app degrades gracefully instead of crashing or corrupting state. It matters for reliability, security (don't leak stack traces to clients), debuggability (good logs/context), and UX (meaningful responses).

The model interviewers want you to map out by error type:

  • Synchronoustry/catch.
  • Promises / async-awaittry/catch around await, or .catch(); surface rejections, never ignore them.
  • Error-first callbacks → check the err argument first.
  • EventEmitters/streams → listen for the 'error' event (an unhandled one throws).
  • Last resortprocess.on('uncaughtException') / process.on('unhandledRejection') for logging, then let the process exit and restart (don't resume).
try { await saveUser(data); }
catch (err) { logger.error(err); res.status(500).json({ error: 'Save failed' }); }

The principles that elevate the answer: distinguish operational errors (expected — handle and recover) from programmer errors (bugs — let it crash and restart via a process manager); use Error subclasses with context; centralize handling (e.g. Express error-handling middleware); and fail fast on truly unknown state rather than limping along.

↑ Back to top

Follow-up 1

Can you explain the difference between operational errors and programmer errors?

In Node.js, operational errors are errors that occur due to external factors such as network issues, file system errors, database connection failures, etc. These errors are usually recoverable and can be handled by implementing appropriate error handling mechanisms.

On the other hand, programmer errors are errors that occur due to mistakes or bugs in the code itself. These errors are usually unrecoverable and may cause the application to crash. Examples of programmer errors include syntax errors, logical errors, and type errors.

Follow-up 2

How does error handling in Node.js differ from other languages?

Error handling in Node.js differs from other languages in a few ways:

  1. Asynchronous nature: Node.js is designed to handle a large number of concurrent requests asynchronously. This asynchronous nature requires a different approach to error handling compared to synchronous languages.

  2. Callback pattern: Node.js commonly uses the callback pattern for handling asynchronous operations. Error handling in Node.js often involves checking for errors in the callback function and handling them accordingly.

  3. Event-driven architecture: Node.js follows an event-driven architecture, where events are emitted and handled asynchronously. Error handling in Node.js often involves listening for error events and handling them appropriately.

  4. Promises and async/await: Node.js also supports Promises and async/await syntax for handling asynchronous operations. These provide a more structured and readable way to handle errors compared to the callback pattern.

Follow-up 3

Can you give an example of how to handle an error in Node.js?

Certainly! Here's an example of how to handle an error in Node.js using the callback pattern:

const fs = require('fs');

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }

  console.log('File contents:', data);
});

In this example, the fs.readFile function is used to read the contents of a file. If an error occurs during the reading process, the callback function is called with the error as the first argument. The error is then logged to the console, and the execution is returned to prevent further processing. If no error occurs, the file contents are logged to the console.

2. What is the role of try/catch in error handling in Node.js?

try/catch handles synchronous errors: code in try that throws transfers control to catch, where you handle it, with an optional finally for cleanup.

The crucial gotcha interviewers test: plain try/catch does NOT catch errors from callbacks or un-awaited promises, because those run later, outside the try block:

try {
  fs.readFile('f.txt', cb);     // ❌ an async error here is NOT caught
} catch (e) { /* never runs */ }

It does work with async/await, because await makes the rejection throw at that point:

try {
  const data = await readFile('f.txt', 'utf8');   // ✅ rejection caught
} catch (err) { logger.error(err); }

So the complete answer: use try/catch for synchronous code and around awaited promises; for callback APIs check the error-first argument instead; and for EventEmitters/streams listen to the 'error' event. A subtle point: a throw inside an async function rejects its returned promise, so the caller must still await/.catch() it.

↑ Back to top

Follow-up 1

Can you give an example of a situation where try/catch would be used in Node.js?

Sure! Here's an example:

try {
    // Code that might throw an error
    const result = someFunction();
    console.log(result);
} catch (error) {
    // Handle the error
    console.error('An error occurred:', error);
}

In this example, the someFunction() function might throw an error. By wrapping the function call in a try block and catching the error in the catch block, we can handle the error and prevent it from crashing the entire application.

Follow-up 2

What happens if an error is not caught in a try/catch block?

If an error is not caught in a try/catch block, it will propagate up the call stack until it is caught by an error handler or it reaches the top level of the application. If the error is not caught at any level, it will cause the Node.js process to terminate and display an error message. This can lead to unexpected application crashes and potential data loss.

Follow-up 3

What are the limitations of try/catch in Node.js?

While try/catch is a powerful error handling mechanism, it has some limitations in Node.js:

  1. Asynchronous errors: try/catch can only handle synchronous errors. It cannot catch errors that occur in asynchronous operations like callbacks or promises. For handling asynchronous errors, you need to use other mechanisms like error-first callbacks or catch method on promises.

  2. Performance impact: Using try/catch can have a performance impact, especially if the try block contains a large amount of code. It is recommended to use try/catch only for handling expected errors and not for flow control.

  3. Limited scope: The catch block can only handle errors that occur within the try block. If an error occurs in a different scope, it will not be caught by the catch block.

3. What are error-first callbacks in Node.js?

Error-first callbacks (a.k.a. "errback") are Node's classic async convention: the callback's first parameter is an error, and the remaining parameters carry the result. If the operation failed, err is an Error; if it succeeded, err is null/undefined and the data follows.

fs.readFile('config.json', 'utf8', (err, data) => {
  if (err) return handle(err);   // always check err first
  use(data);
});

Why it exists and what to know: it standardized error handling across core callback APIs (fs, dns, etc.) before promises existed. The rule is always check err first and return early so you don't use undefined data.

The current framing that signals up-to-date knowledge: most modern code prefers promises / async-await, and Node provides promise versions of core modules (fs/promises) plus util.promisify to convert an error-first function into one that returns a promise. So error-first callbacks are foundational and still common in older APIs, but new code typically wraps or avoids them in favor of await + try/catch.

↑ Back to top

Follow-up 1

Can you explain how error-first callbacks work with an example?

Sure! Here's an example of how error-first callbacks work:

function readFile(path, callback) {
  fs.readFile(path, 'utf8', function(err, data) {
    if (err) {
      callback(err);
    } else {
      callback(null, data);
    }
  });
}

readFile('file.txt', function(err, data) {
  if (err) {
    console.error('Error:', err);
  } else {
    console.log('File content:', data);
  }
});

Follow-up 2

What are the advantages of using error-first callbacks?

There are several advantages of using error-first callbacks in Node.js:

  1. Consistent error handling: Error-first callbacks provide a consistent way to handle errors in asynchronous functions. Developers can easily identify and handle errors by checking the first argument of the callback.

  2. Error propagation: Error-first callbacks allow errors to be propagated up the callback chain. If an error occurs in a nested callback, it can be passed to the parent callback and handled accordingly.

  3. Error stack trace: Error-first callbacks preserve the stack trace of the error, making it easier to debug and trace the source of the error.

  4. Compatibility: Error-first callbacks are widely used in the Node.js ecosystem, and many libraries and frameworks follow this convention. Using error-first callbacks ensures compatibility and interoperability with other Node.js modules.

Follow-up 3

How do error-first callbacks contribute to the asynchronous nature of Node.js?

Error-first callbacks are an integral part of the asynchronous nature of Node.js. By using error-first callbacks, Node.js allows multiple asynchronous operations to be executed concurrently without blocking the event loop. When an asynchronous function completes, it invokes the callback with either an error or the result. This callback-based approach enables non-blocking I/O operations and efficient handling of multiple concurrent requests. The error-first callback convention ensures that errors are properly handled and propagated throughout the asynchronous flow, making Node.js a highly scalable and performant platform for building server-side applications.

4. How can unhandled exceptions be dealt with in Node.js?

There are two "uncaught" cases, and modern Node treats both as fatal by default:

  • process.on('uncaughtException', handler) — a synchronous error that bubbled to the top with no try/catch.
  • process.on('unhandledRejection', handler) — a rejected promise nobody .catch()/awaited. Since Node 15 this crashes the process by default (it used to only warn).
process.on('uncaughtException', (err) => {
  logger.fatal(err);
  process.exit(1);     // log, then exit — do not resume
});

The critical guidance interviewers look for: these handlers are a last resort for logging and graceful shutdown, not for recovery. After an uncaught exception the process is in an unknown, possibly corrupted state, so you should log, flush, and exit, then let a process manager (PM2, systemd) or your container orchestrator (Kubernetes) restart a clean instance. The real fix is handling errors at the source — try/catch around await, .catch() on promises, and 'error' listeners on streams/emitters — so almost nothing reaches these global handlers.

↑ Back to top

Follow-up 1

What is the 'uncaughtException' event and how can it be used?

The 'uncaughtException' event is emitted when an unhandled exception occurs in Node.js. It can be used to catch and handle the exception before the process exits. Here is an example of using the 'uncaughtException' event:

process.on('uncaughtException', (err) => {
  console.error('An uncaught exception occurred:', err);
  // Perform any necessary cleanup or error handling
});

Follow-up 2

What are the risks of not handling exceptions?

Not handling exceptions in Node.js can lead to unexpected application crashes and potential data corruption. It can also make it difficult to identify and debug issues, as the error information may not be logged or displayed to the user.

Follow-up 3

Can you give an example of handling an unhandled exception in Node.js?

Sure! Here is an example of handling an unhandled exception in Node.js using the 'uncaughtException' event:

process.on('uncaughtException', (err) => {
  console.error('An uncaught exception occurred:', err);
  // Perform any necessary cleanup or error handling
});

// Simulating an unhandled exception
throw new Error('Oops!');

5. What is the 'error' event in Node.js and how is it used?

The 'error' event is the standard way EventEmitter-based objects report failures — streams, sockets, servers, child processes, etc. Because these objects work asynchronously, they can't throw to a try/catch; instead they emit 'error', and you handle it with a listener.

const stream = fs.createReadStream('missing.txt');
stream.on('error', (err) => console.error('read failed:', err.message));
stream.on('data', (chunk) => process(chunk));

The gotcha that makes this a favorite interview point: if an EventEmitter emits 'error' and no listener is attached, Node throws the error and typically crashes the process. So you must always attach an 'error' handler to streams, servers, and sockets.

Best-practice additions: when piping streams, prefer stream.pipeline() (or pipeline from node:stream/promises) over .pipe(), because it propagates errors and cleans up all streams automatically — manual .pipe() chains silently leak on error. This event-based error channel complements try/catch (sync) and promise rejection (async one-shot).

↑ Back to top

Follow-up 1

Can you give an example of how the 'error' event is used?

Sure! Here's an example of how the 'error' event is used to handle errors in a file read operation:

const fs = require('fs');

const readStream = fs.createReadStream('file.txt');

readStream.on('error', (error) => {
  console.error('An error occurred:', error);
});

Follow-up 2

What happens if an 'error' event is emitted without being caught?

If an 'error' event is emitted without being caught, it will cause the Node.js process to terminate by default. This behavior can be changed by attaching an 'error' event listener to the 'process' object and handling the error event. However, it is generally recommended to always handle errors to prevent unexpected termination of the application.

Follow-up 3

How does the 'error' event help in error propagation and handling?

The 'error' event helps in error propagation and handling by providing a standardized way to handle errors in Node.js applications. When an error occurs, it can be emitted as an 'error' event, allowing the error to be caught and handled by the appropriate error handler. This helps in propagating the error to the appropriate level of the application and allows for centralized error handling. Additionally, the 'error' event can be used to handle asynchronous errors, such as errors that occur during file I/O or network operations.

Live mock interview

Mock interview: Error Handling in Node.js

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.