Interview Questions
Node.js Interview Questions and Answers
Node.js interviews center heavily on the event loop and asynchronous programming -- understanding why Node handles concurrent I/O the way it does is the core conceptual bar.
Example: Callback vs. Promise vs. async/await for the same operation
JavaScript// Callback style (older)
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log(data);
});
// Promise style
fs.promises.readFile('data.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
// async/await (built on Promises, reads like synchronous code)
async function readData() {
try {
const data = await fs.promises.readFile('data.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
Frequently Asked Questions
Node.js runs JavaScript on a single thread, but handles I/O (file reads, network requests, database queries) asynchronously via the event loop -- when you start an I/O operation, Node hands it off (often to the OS or a thread pool) and continues executing other code; when the operation completes, its callback is queued and the event loop picks it up. This is how Node handles many concurrent connections efficiently without spawning a thread per request.
The JavaScript execution itself runs on a single main thread, yes -- but Node uses libuv's thread pool under the hood for certain operations (file system access, some crypto functions), and I/O itself is handled by the OS asynchronously, not blocking that main thread. This is why a single slow, synchronous, CPU-bound operation on the main thread can block the entire event loop -- true parallel CPU work needs worker threads or a separate process.
All three handle asynchronous operations. Callbacks are the original pattern but nest badly ("callback hell") for sequential async steps. Promises represent a future value and chain more cleanly with .then()/.catch(). async/await is syntax built on top of Promises that lets asynchronous code read like synchronous code, generally considered the clearest style for sequential async logic today.
A function with access to the request, response, and a next() function, executed in sequence for each incoming request -- used for cross-cutting concerns like logging, authentication, body parsing, or error handling. Each middleware either ends the request-response cycle or calls next() to pass control to the following middleware.
A way to process data incrementally, in chunks, rather than loading an entire file/response into memory at once -- important for handling large files or data transfers efficiently. Readable, Writable, Duplex, and Transform streams can be piped together (readable.pipe(writable)) to build efficient data-processing pipelines.
Both defer execution, but at different points in the event loop. process.nextTick() callbacks run immediately after the current operation completes, before the event loop continues to its next phase -- effectively highest priority. setImmediate() callbacks run in a later phase of the event loop, after I/O events. The distinction rarely matters for typical application code but occasionally comes up in interviews to test event loop understanding.
Always attach a .catch() (or wrap await calls in try/catch) rather than letting rejections go unhandled -- an unhandled rejection can crash a Node process in current versions (process.on('unhandledRejection', ...) can be used as a last-resort safety net/logging point, but shouldn't be relied on as the primary error-handling strategy).