Node JS Interview Questions With Answers and Working Code
Prepare for node js interview questions with concise explanations, runnable examples, event-loop reasoning, API design scenarios, and follow-up prompts.

Node js interview questions test more than syntax. You need to explain how the runtime schedules work, write asynchronous code that handles failure, and defend backend design choices without hiding behind framework defaults.
The strongest answers separate JavaScript behavior from Node.js behavior. They also include the trade-off, the failure mode, and a small working example.
What a Node.js Interview Usually Tests#
A Node.js interview usually covers JavaScript, the Node.js runtime, and backend engineering judgment as separate skills.
You may encounter several question formats:
- Direct concepts: Explain a stream, closure, module, or event-loop phase.
- Output prediction: Trace synchronous code, promises, timers, and callbacks.
- Debugging: Find a missing
return, unhandled rejection, blocked event loop, or duplicate response. - Coding: Implement concurrency control, file processing, caching, or request aggregation.
- API design: Discuss validation, authentication, retries, database access, and failure handling.
JavaScript questions cover closures, prototypes, promises, coercion, and language syntax. Node.js questions cover process, Buffer, modules, streams, libuv, and runtime scheduling. Backend questions ask whether your API behaves correctly under failure and concurrent use.
Seniority changes the depth, not necessarily the topic. A junior candidate may identify middleware order. A mid-level candidate may explain error propagation. A senior candidate may discuss shutdown behavior, request cancellation, observability, and ownership boundaries. No single rubric applies to every company.
For broader algorithm practice, use the LeetCode reference. For interview format and platform documentation, see the platform reference.
Node.js Fundamentals Questions and Answers#
These Node.js interview questions and answers establish whether you understand what Node provides beyond JavaScript.
What is Node.js?#
Node.js is a JavaScript runtime built around the V8 engine, with runtime APIs for files, networking, processes, streams, and asynchronous I/O.
V8 parses, compiles, and executes JavaScript. Node adds bindings and libraries around it. libuv provides the event loop, a worker pool, and abstractions over operating-system I/O.
Likely follow-up: Is Node.js single-threaded?
Your JavaScript usually runs on one main thread. The process can still use operating-system facilities, libuv workers, worker threads, and child processes. “Single-threaded” does not mean the entire runtime performs every operation on one thread.
What is the difference between CommonJS and ES modules?#
CommonJS uses require and module.exports; ES modules use import and export.
CommonJS modules load through Node’s older module system. ES modules follow the JavaScript module standard. Module configuration affects file interpretation, resolution, top-level variables, and loading behavior.
// CommonJS
const fs = require("node:fs");
module.exports = { read: fs.readFile };// ES moduleLikely follow-up: Is __dirname available in ES modules?
Not as a built-in module variable. In an ES module, derive a location from import.meta.url when needed.
What are process, Buffer, and globalThis?#
They are runtime-provided globals, not features of the JavaScript language itself.
processexposes information and controls related to the current Node process.Bufferrepresents binary data.globalThisrefers to the global object in a standard, cross-environment form.
Browsers provide objects such as window and document. Node does not provide them by default.
Likely follow-up: Why use Buffer instead of a string?
A Buffer preserves raw bytes. Text requires an encoding such as UTF-8, while files and network protocols may contain arbitrary binary data.
Why commit a package lock file?#
A package lock records the resolved dependency graph so installations can reproduce the same dependency choices.
Your package manifest describes intended dependency ranges and scripts. The lock file records concrete resolutions. You should also understand that lifecycle scripts execute code during package operations, so dependency review remains part of backend security.
The Event Loop, Promises, and Async Execution#
Node.js event loop interview questions test whether you can reason about scheduling rather than repeat “non-blocking I/O.”
JavaScript begins on the call stack. Synchronous functions run until the stack is empty. Asynchronous operations arrange for later work, which enters the appropriate queue when ready.
Important event-loop areas include:
- Timers: callbacks scheduled by
setTimeoutandsetInterval. - Poll: many I/O callbacks become eligible here.
- Check:
setImmediatecallbacks run here. - Close callbacks: certain resource-close events run here.
- Microtasks: promise reactions and
queueMicrotaskcallbacks. - The next-tick queue: callbacks registered with
process.nextTick.
process.nextTick is not an event-loop phase. Node drains it at specific boundaries before continuing with regular event-loop work. Promise callbacks also run before moving on to later timer or I/O work, though execution context matters when comparing queues.
A useful asynchronous JavaScript interview answer traces the code:
- Run synchronous statements.
- Record what each asynchronous call schedules.
- Drain the relevant next-tick and microtask work.
- Continue through eligible event-loop phases.
- Avoid claiming an order that the surrounding context does not guarantee.
CPU work is different from asynchronous I/O. Waiting for a socket does not require the main JavaScript stack to remain busy. A large synchronous calculation does. While that calculation runs, the event loop cannot execute request handlers, timers, or completed callbacks on the main thread.
For CPU-heavy work, discuss worker threads, child processes, smaller work units, or moving the computation to another service. The right choice depends on isolation, serialization cost, and operational complexity.
Worked Example: Predict the Execution Order#
The deterministic part of this example is the synchronous output followed by the next-tick and promise callbacks.
Save this as a CommonJS file and run it with Node:
console.log("start");
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
console.log("end");Trace it line by line:
"start"prints immediately.setTimeoutschedules a timer callback.setImmediateschedules work for the check phase.process.nextTickadds a next-tick callback..thenadds a promise reaction."end"prints immediately.- The current synchronous work completes.
- Node runs the next-tick callback.
- Node runs the promise callback.
- The timer and immediate callbacks run when their phases become eligible.
The guaranteed prefix is:
start
end
nextTick
promiseDo not assume a universal order between "timeout" and "immediate" in this top-level example. Runtime details and execution context can affect which becomes eligible first.
Inside some I/O callbacks, setImmediate commonly runs before a newly scheduled zero-delay timer because the event loop proceeds from poll to check. Explain the context instead of memorizing one output for every case.
Streams, Buffers, and Backpressure Questions#
Node.js streams interview questions usually test whether you can process incremental data without manually loading everything before starting.
The stream types are:
- Readable: produces data, such as a file read stream.
- Writable: consumes data, such as an HTTP response.
- Duplex: reads and writes, such as a socket.
- Transform: a duplex stream that changes data, such as compression.
Backpressure occurs when a producer can provide data faster than a consumer currently accepts it. The producer needs a signal to pause instead of continually adding unfinished writes.
Use pipeline when connecting production streams because it coordinates completion, errors, and teardown more safely than a bare chain of pipe calls.
const fs = require("node:fs");
const { pipeline } = require("node:stream/promises");
const { createGzip } = require("node:zlib");
async function compress(input, output) {
await pipeline(
fs.createReadStream(input),
createGzip(),
fs.createWriteStream(output)
);
}pipe connects a readable stream to a writable stream and handles normal flow control. You still need careful error and cleanup handling across the full chain. pipeline provides one completion point that rejects when the operation fails.
A likely follow-up is how manual writes handle backpressure. If writable.write(chunk) returns false, stop producing and wait for the drain event before continuing.
API, Express, and Error-Handling Questions#
Express.js interview questions focus on request flow, ownership, and what happens when code fails halfway through a response.
Middleware runs in registration order. Place broad concerns deliberately:
- Request context and logging.
- Parsing.
- Authentication.
- Route-specific authorization and validation.
- Route handlers.
- Not-found handling.
- Centralized error handling.
Validate untrusted input at the system boundary. Authentication establishes identity. Authorization decides whether that identity can perform a specific action on a specific resource.
Async failures must reach the error-handling path. Depending on your Express version and conventions, that may require calling next(error) or using a wrapper that forwards rejected promises.
app.get("/users/:id", async (req, res, next) => {
try {
const user = await findUser(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
return res.json(user);
} catch (error) {
next(error);
}
});A common debugging scenario is “headers already sent.” This handler sends two responses:
if (!user) {
res.status(404).json({ error: "Not found" });
}
res.json(user);Sending a response does not automatically return from the function. Add return or structure the branches so only one response path can execute.
Operational failures include unavailable dependencies, timeouts, invalid requests, and exhausted resources. Programmer errors include broken invariants and incorrect assumptions in code. You normally handle expected operational failures explicitly. A programmer error may require controlled process replacement after logging enough context.
Graceful shutdown usually means stopping new requests, allowing bounded in-flight work to finish, closing resources, and then exiting. It must also have a deadline so shutdown cannot wait forever.
Worked Coding Problem: Build a Concurrency-Limited Task Runner#
A concurrency-limited runner starts no more than the requested number of asynchronous tasks while preserving input order in the result array.
A simple sequential loop preserves order but allows no overlap. Starting every task with Promise.all removes the limit. A worker pool gives you a small shared queue without repeatedly scanning the task list.
async function runLimited(tasks, limit) {
if (!Number.isInteger(limit) || limit < 1) {
throw new RangeError("limit must be a positive integer");
}
const results = new Array(tasks.length);
let nextIndex = 0;
let firstError;
async function worker() {
while (!firstError) {
const index = nextIndex++;
if (index >= tasks.length) return;
try {
results[index] = await tasks[index]();
} catch (error) {
firstError = error;
}
}
}
const count = Math.min(limit, tasks.length);
const workers = Array.from({ length: count }, () => worker());
await Promise.all(workers);
if (firstError) throw firstError;
return results;
}Runnable usage:
const wait = (value, delay) => () =>
new Promise(resolve => setTimeout(() => resolve(value), delay));
runLimited([wait("A", 30), wait("B", 10), wait("C", 20)], 2)
.then(console.log)
.catch(console.error);Tasks may finish out of order. Results remain in input order because each worker writes to the original task index.
The time complexity is O(n) for task scheduling, excluding the work performed inside each task. The auxiliary space complexity is O(n) for results and worker bookkeeping.
When one task rejects, this implementation stops workers from pulling more tasks. Already running tasks cannot be forcibly canceled by a promise alone. A strong follow-up answer discusses AbortSignal, collecting all outcomes, lazy task iterators, per-task timeouts, and whether queued work should continue after failure.
This exercise connects Node.js concurrency to resource limits. Concurrency does not require JavaScript statements to execute simultaneously on the main thread. Multiple I/O operations can remain in progress while the event loop schedules their completions.
Database and Production-Readiness Questions#
A Node.js backend interview usually ends with trade-offs around persistence, failures, and unfinished work.
- Connection pooling: Reuse a bounded set of database connections. A pool avoids opening a new connection per request, but a pool that is too large can overload the database.
- Transactions: Use them when several writes must succeed or fail as one unit. Keep them focused because long transactions hold resources and increase contention.
- Idempotency: Give retryable operations a stable key or business identifier so repeated requests do not repeat the effect.
- Pagination: Offset pagination is simple but can become inconsistent as rows change. Cursor pagination provides a more stable continuation when backed by an appropriate ordering and index.
- Caching: Define ownership, expiration, invalidation, and acceptable staleness before adding a cache. A cache can reduce dependency work while creating another consistency boundary.
- Timeouts: Every remote call should have a bounded waiting policy. A timeout limits waiting; it does not prove the remote operation never completed.
- Retries: Retry only suitable failures, with limits and spacing. Retrying non-idempotent work can duplicate side effects.
- Structured logging: Emit named fields such as request ID, operation, duration, and error category. Avoid secrets and raw credentials.
Consider an API that remains responsive but steadily accumulates unfinished work. Start by checking what is created faster than it completes:
- Promises waiting on dependencies.
- Requests without timeouts.
- Database pool waiters.
- Queued background jobs.
- Streams ignoring backpressure.
- Event listeners that are never removed.
- Retry loops producing more work.
Then trace one request across its boundaries. Record when work enters a queue, acquires a resource, finishes, times out, or is canceled. Responsiveness alone does not mean the process is healthy. The important question is whether outstanding work returns to a stable level after demand subsides.
Frequently asked questions
- What is Node.js?
- Node.js is a JavaScript runtime built around V8, with APIs for files, networking, processes, streams, and asynchronous I/O. It also uses libuv for the event loop, worker pool, and operating-system I/O abstractions.
- Is Node.js single-threaded?
- JavaScript usually runs on one main thread, but the Node.js process can also use operating-system facilities, libuv workers, worker threads, and child processes.
- What is the difference between CommonJS and ES modules?
- CommonJS uses `require` and `module.exports`, while ES modules use `import` and `export`. The selected module system affects file interpretation, resolution, top-level variables, and loading behavior.
- Does process.nextTick run before promise callbacks in Node.js?
- In the article’s top-level example, Node runs the `process.nextTick` callback before the promise reaction after synchronous work finishes. Execution context still matters when comparing asynchronous queues.
- What is backpressure in Node.js streams?
- Backpressure occurs when a producer supplies data faster than a consumer can accept it. For manual writes, pause when `writable.write(chunk)` returns `false` and resume after the `drain` event.
Keep reading

Azure Interview Questions With Answers and Scenarios
Prepare for Azure interviews by connecting workload requirements to service choices, trade-offs, security, recovery, and operations.

Cracking the Coding Interview PDF: Legal Access Guide
Learn how to verify legal digital access, choose the right edition and format, avoid unsafe mirrors, and turn the book into active practice.

Grokking the Coding Interview: A Practical Study Guide
Use Grokking the Coding Interview as a pattern-first framework built on cold attempts, retrieval, variations, and mixed practice.