Top 50 Node.js Interview Questions and Answers
Commonly asked Node.js interview questions, from fundamentals to advanced concepts.
1.What is Node.js?
Node.js is a runtime environment that executes JavaScript outside the browser, built on Chrome's V8 engine.
- Enables building server-side applications, CLIs, and tools using JavaScript.
- Uses a non-blocking, event-driven I/O model, making it well-suited for scalable network applications.
2.What is the Event Loop in Node.js?
The Event Loop is the mechanism that allows Node.js to perform non-blocking I/O despite JavaScript being single-threaded.
- Continuously checks the call stack and callback queue, pushing queued callbacks onto the stack once it's empty.
- Enables Node.js to handle many concurrent operations (file reads, network requests) without spawning a thread per request.
3.What is the difference between synchronous and asynchronous code in Node.js?
They differ in whether execution blocks while waiting for an operation to complete:
- Synchronous: each operation completes before the next line runs — blocks the single thread.
- Asynchronous: operations (like file I/O or network calls) run in the background, and execution continues immediately; results are handled later via callbacks, promises, or
async/await.
fs.readFileSync('file.txt'); // blocks
fs.readFile('file.txt', (err, data) => {}); // non-blocking
4.What is a Callback function in Node.js?
A Callback is a function passed as an argument to another function, invoked once an asynchronous operation completes.
fs.readFile('data.txt', (err, data) => {
if (err) throw err;
console.log(data.toString());
});
- The traditional Node.js pattern before Promises/async-await became common, still used by many core APIs and older libraries.
5.What is Callback Hell, and how do you avoid it?
Callback Hell refers to deeply nested callbacks that become hard to read and maintain, often shaped like a pyramid.
getUser(id, (user) => {
getOrders(user.id, (orders) => {
getDetails(orders[0].id, (details) => { ... });
});
});
- Avoided by using Promises and async/await, which flatten asynchronous code into a more linear, readable structure.
6.What are Promises in Node.js/JavaScript?
A Promise represents the eventual result (or failure) of an asynchronous operation.
const promise = fetchData();
promise.then(result => console.log(result))
.catch(err => console.error(err));
- Has three states: pending, fulfilled, or rejected — providing a cleaner alternative to nested callbacks.
7.What is async/await in Node.js?
async/await is syntactic sugar over Promises, letting asynchronous code be written in a synchronous-looking style.
async function getUser(id) {
try {
const user = await db.findUser(id);
return user;
} catch (err) {
console.error(err);
}
}
awaitpauses execution of theasyncfunction (not the whole program) until the awaited Promise resolves.
8.What is the difference between process.nextTick() and setImmediate()?
Both schedule a callback to run later, but at different points in the event loop:
- process.nextTick(): runs immediately after the current operation completes, before the event loop continues — has higher priority.
- setImmediate(): runs in the check phase of the event loop, after I/O events are processed.
- Overusing
process.nextTick()can starve the event loop, since it always runs before I/O callbacks.
9.What is npm, and what is package.json used for?
npm (Node Package Manager) is the default package manager for Node.js, used to install and manage dependencies.
package.jsonis the project manifest, declaring dependencies, scripts, and metadata (name, version, entry point).
{
"name": "my-app",
"dependencies": { "express": "^4.18.0" },
"scripts": { "start": "node index.js" }
}
10.What is the difference between dependencies and devDependencies in package.json?
Both list packages a project needs, but for different purposes:
- dependencies: required to run the application in production (e.g.,
express). - devDependencies: only needed during development (e.g., testing frameworks, linters, build tools) — not installed in a production-only install (
npm install --production).
11.What is the require() function in Node.js, and how does it differ from ES Modules import?
Both load modules, but use different systems:
- require(): CommonJS module system — synchronous, resolved at runtime.
- import: ES Modules (ESM) — supports static analysis, tree-shaking, and top-level
await, resolved before execution.
const fs = require('fs'); // CommonJS
import fs from 'fs'; // ES Modules
12.What is the difference between CommonJS and ES Modules in Node.js?
They're two different module systems, both supported by modern Node.js:
- CommonJS: uses
require/module.exports, loads modules synchronously, historically the Node.js default. - ES Modules (ESM): uses
import/export, the standard JavaScript module system supported in browsers too — enabled in Node.js via.mjsfiles or"type": "module"in package.json.
13.What is the purpose of the Buffer class in Node.js?
Buffer handles raw binary data directly, outside the V8 heap.
const buf = Buffer.from('hello', 'utf-8');
console.log(buf); // <Buffer 68 65 6c 6c 6f>
- Essential for working with file I/O, network streams, and binary protocols where raw byte data must be manipulated.
14.What are Streams in Node.js?
Streams process data incrementally (in chunks), rather than loading everything into memory at once.
- Readable: data can be read from (e.g., file reads, HTTP requests).
- Writable: data can be written to (e.g., file writes, HTTP responses).
- Duplex: both readable and writable (e.g., TCP sockets).
- Transform: a duplex stream that modifies data as it passes through (e.g., compression).
fs.createReadStream('big.txt').pipe(fs.createWriteStream('copy.txt'));
15.What is the purpose of the pipe() method in Node.js streams?
pipe() connects a readable stream's output directly to a writable stream's input, automatically handling backpressure.
readableStream.pipe(writableStream);
- Prevents memory issues that could occur if data is read faster than it can be written, by automatically pausing/resuming the source stream as needed.
16.What is Express.js?
Express.js is a minimal, flexible web framework for Node.js, used to build APIs and web applications.
const express = require('express');
const app = express();
app.get('/users', (req, res) => res.json([{ id: 1 }]));
app.listen(3000);
- Provides routing, middleware support, and a simple API on top of Node's built-in
httpmodule.
17.What is Middleware in Express.js?
Middleware functions have access to the request, response, and the next() function, and execute in sequence before the final route handler.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // pass control to the next middleware
});
- Used for logging, authentication, parsing request bodies, and error handling.
18.What is the difference between app.use() and app.get() in Express?
Both register handlers, but for different purposes:
- app.use(): registers middleware for all HTTP methods, optionally scoped to a path prefix.
- app.get(): registers a handler specifically for GET requests on an exact route path.
19.How do you handle errors in Express.js?
Express supports dedicated error-handling middleware, identified by having four parameters.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong' });
});
- Errors in synchronous route handlers are caught automatically; async errors must be passed to
next(err)explicitly (in Express 4) or are handled automatically (in Express 5).
20.What is the difference between process.env and a .env file in Node.js?
Both relate to environment configuration:
- process.env: a Node.js object exposing all current environment variables at runtime.
- .env file: a plain text file storing key-value pairs, typically loaded into
process.envusing a library likedotenvduring local development. - Production environments usually set variables directly (via the hosting platform) rather than relying on a
.envfile.
21.What is clustering in Node.js, and why is it needed?
Clustering allows a Node.js application to spawn multiple worker processes, each running on a separate CPU core.
const cluster = require('cluster');
if (cluster.isPrimary) {
for (let i = 0; i < numCPUs; i++) cluster.fork();
}
- Needed because Node.js runs JavaScript on a single thread by default — clustering lets a multi-core machine handle more concurrent load.
22.What is the difference between the Cluster module and Worker Threads in Node.js?
Both enable parallelism, but for different use cases:
- Cluster: spawns multiple separate processes, each with its own memory and event loop, mainly for scaling network servers across CPU cores.
- Worker Threads: run JavaScript in separate threads within the same process, sharing memory via
SharedArrayBuffer— better suited for CPU-intensive computation without blocking the main thread.
23.What is the purpose of the package-lock.json file?
package-lock.json records the exact versions of every installed dependency (including nested dependencies).
- Ensures that
npm installproduces an identical dependency tree across different machines and CI environments, avoiding "works on my machine" issues from mismatched transitive dependency versions. - Should be committed to version control.
24.What is the difference between npm install and npm ci?
Both install dependencies, but behave differently:
- npm install: reads
package.json, may updatepackage-lock.jsonif versions have drifted. - npm ci: installs strictly from
package-lock.jsonas-is (failing if it's out of sync withpackage.json), and deletesnode_modulesfirst — faster and more reliable for CI/CD pipelines.
25.What is REPL in Node.js?
REPL (Read-Eval-Print Loop) is an interactive shell for executing JavaScript line by line.
$ node
> 1 + 1
2
- Useful for quickly testing snippets of code or exploring an API without creating a full script file.
26.What is the difference between exports and module.exports in Node.js?
Both relate to exporting values from a CommonJS module, but with a subtle distinction:
module.exportsis the actual object returned byrequire().exportsis just a reference tomodule.exports— reassigningexports = {...}breaks that reference, whilemodule.exports = {...}correctly changes what's exported.
27.What is the purpose of the Node.js fs module?
The fs (File System) module provides APIs for interacting with the file system — reading, writing, deleting, and watching files.
const fs = require('fs');
fs.readFile('data.txt', 'utf8', (err, data) => console.log(data));
- Offers both synchronous (
readFileSync) and asynchronous (readFile) versions of most operations, plus a Promise-based variant (fs/promises).
28.What is the purpose of the Node.js path module?
The path module provides utilities for working with file and directory paths cross-platform (handling differences between / and \).
const path = require('path');
path.join('/users', 'alice', 'file.txt'); // /users/alice/file.txt
path.extname('file.txt'); // .txt
29.What is the difference between __dirname and process.cwd()?
Both return directory paths, but represent different things:
- __dirname: the directory containing the currently executing file — fixed regardless of where the script was run from.
- process.cwd(): the current working directory of the Node.js process — depends on where the command was invoked from in the terminal.
30.What is a Memory Leak in Node.js, and what commonly causes it?
A Memory Leak occurs when memory that's no longer needed is never released, causing the process's memory usage to grow over time.
- Common causes: global variables accumulating data, forgotten event listeners, unbounded caches, and closures unintentionally retaining references to large objects.
- Diagnosed using tools like Chrome DevTools' heap snapshot or
--inspectwith Node.js.
31.What is the difference between Node.js and browser JavaScript environments?
Both run JavaScript, but with different available APIs:
- Node.js: provides access to the file system, OS, and networking (
fs,http,process), but has nowindow,document, or DOM. - Browser: provides DOM APIs and browser-specific globals (
window,document), but restricts file system/OS access for security.
32.What is the purpose of the EventEmitter class in Node.js?
EventEmitter implements the publish-subscribe pattern, letting objects emit named events that other code can listen for.
const EventEmitter = require('events');
const emitter = new EventEmitter();
emitter.on('greet', (name) => console.log(`Hello, ${name}`));
emitter.emit('greet', 'Alice');
- Many core Node.js APIs (streams, HTTP servers) are built on top of
EventEmitter.
33.What is the difference between GET, POST, PUT, PATCH, and DELETE HTTP methods in an Express API?
They represent standard REST semantics:
- GET: retrieve a resource, no side effects.
- POST: create a new resource.
- PUT: replace an entire resource.
- PATCH: partially update a resource.
- DELETE: remove a resource.
34.What is CORS, and how do you enable it in a Node.js/Express app?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism controlling which origins are allowed to make requests to your API.
const cors = require('cors');
app.use(cors({ origin: 'https://example.com' }));
- Without proper CORS headers, browsers block cross-origin requests from frontend JavaScript, even if the server itself would respond fine.
35.What is JWT-based authentication, and how is it commonly implemented in Node.js APIs?
JWT authentication issues a signed token after login, which the client sends on subsequent requests to prove identity.
const token = jwt.sign({ userId: user.id }, secret, { expiresIn: '1h' });
// verify on protected routes:
jwt.verify(token, secret);
- Popular libraries:
jsonwebtokenfor signing/verifying, often combined with middleware that checks theAuthorizationheader on protected routes.
36.What is the difference between session-based and token-based authentication?
They differ in where authentication state is stored:
- Session-based: the server stores session data (often in memory or Redis) and gives the client a session ID cookie to reference it — stateful.
- Token-based (e.g., JWT): all necessary information is encoded in the token itself, verified cryptographically — stateless, easier to scale across multiple servers.
37.What is a Rate Limiter, and how is it typically implemented in an Express app?
A Rate Limiter restricts how many requests a client can make within a time window, protecting against abuse or overload.
const rateLimit = require('express-rate-limit');
app.use(rateLimit({ windowMs: 60000, max: 100 }));
- Often backed by an in-memory store for single-server apps, or Redis for distributed rate limiting across multiple server instances.
38.What is the purpose of environment-specific configuration in a Node.js app (dev, staging, production)?
Environment-specific configuration allows the same codebase to behave appropriately in different contexts.
- Examples: different database URLs, API keys, logging verbosity, or feature flags per environment.
- Typically managed via
process.envvariables set differently per deployment target, avoiding hardcoded values in source code.
39.What is the difference between unit testing and integration testing in a Node.js application?
They test different scopes:
- Unit testing: tests individual functions/modules in isolation, often mocking dependencies (e.g., using Jest or Mocha).
- Integration testing: tests how multiple components work together (e.g., an API endpoint hitting a real or test database), catching issues unit tests might miss.
40.What is the purpose of the Node.js cluster's 'sticky sessions' concept?
Sticky sessions ensure a client's requests are always routed to the same worker process in a clustered/load-balanced Node.js setup.
- Necessary when session data is stored in-memory on a specific worker — without stickiness, a request might hit a different worker with no knowledge of that session.
- Often handled at the load balancer level, or avoided entirely by storing sessions in a shared store like Redis.
41.What is the difference between synchronous and asynchronous error handling in Node.js?
They require different handling mechanisms:
- Synchronous errors: caught with a standard
try/catchblock around the code that might throw. - Asynchronous errors: must be handled within the callback, via
.catch()on a Promise, or withtry/catcharound anawaitexpression — a plaintry/catcharound an async call withoutawaitwon't catch a rejected promise.
42.What is the purpose of the Node.js child_process module?
child_process lets Node.js spawn and interact with other OS processes.
const { exec } = require('child_process');
exec('ls -la', (err, stdout) => console.log(stdout));
- Useful for running shell commands, other executables, or CPU-heavy scripts outside the main Node.js event loop.
43.What is the difference between spawn(), exec(), and fork() in child_process?
All three create child processes, but for different purposes:
- spawn(): launches a command, streaming data — good for large output, doesn't buffer the whole result in memory.
- exec(): runs a command in a shell and buffers the entire output — convenient for small outputs, but risky for large ones (memory limits).
- fork(): spawns a new Node.js process specifically, with a built-in IPC channel for message passing between parent and child.
44.What is the purpose of a reverse proxy (like Nginx) in front of a Node.js application?
A reverse proxy sits between clients and the Node.js server, handling tasks the application shouldn't do itself.
- Load balancing across multiple Node.js instances.
- SSL/TLS termination, serving HTTPS while the app itself only handles plain HTTP.
- Serving static files efficiently, and providing caching, compression, and basic security filtering.
45.What is the difference between horizontal and vertical scaling for a Node.js application?
They represent two different scaling strategies:
- Vertical scaling: adding more CPU/RAM to a single server — simple, but has hardware limits and a single point of failure.
- Horizontal scaling: running multiple instances of the application across several servers/processes, often behind a load balancer — better fault tolerance and near-unlimited scaling, but requires stateless design (or shared external session storage).
46.What is the purpose of the Node.js util.promisify() function?
util.promisify() converts a traditional Node.js callback-based function into one that returns a Promise, so it can be used with async/await.
const util = require('util');
const readFile = util.promisify(fs.readFile);
const data = await readFile('file.txt', 'utf8');
47.What is the significance of the Node.js LTS (Long-Term Support) release line?
LTS releases are Node.js versions guaranteed to receive bug fixes and security patches for an extended period (typically ~30 months).
- Recommended for production applications, since they prioritize stability over the newest experimental features found in "Current" releases.
- Following the LTS schedule helps teams plan upgrades predictably rather than chasing every new release.
48.What is the purpose of a health check endpoint in a Node.js API?
A health check endpoint (e.g., GET /health) reports whether the application (and its dependencies, like a database connection) is running correctly.
app.get('/health', (req, res) => res.status(200).json({ status: 'ok' }));
- Used by load balancers and orchestration platforms (like Kubernetes) to determine whether to route traffic to an instance or restart it.
49.What is the difference between throwing an error and calling next(err) in Express?
Both signal an error, but behave differently depending on context:
- Throwing inside a synchronous route handler is automatically caught by Express and routed to error-handling middleware.
- Inside asynchronous code (a callback or unhandled promise rejection), a thrown error is not automatically caught — you must explicitly call
next(err)(in Express 4) to pass it to the error handler.
50.What is the purpose of the cluster-aware 'graceful shutdown' pattern in Node.js servers?
Graceful shutdown ensures a Node.js server finishes in-flight requests and cleans up resources before exiting, instead of terminating abruptly.
process.on('SIGTERM', async () => {
server.close(() => process.exit(0));
});
- Prevents dropped requests during deployments or container restarts, and allows database connections/queues to be closed cleanly.
- Especially important in orchestrated environments (like Kubernetes) that send
SIGTERMbefore forcefully killing a process.
