Interview Q&A · 102 questions · 21 answered publicly
Node.js Developer Interview Questions
Ordered by how often they're asked. Sign in free to see every answer, rate your confidence and run timed mock interviews.
-
Most asked What is Node.js and why is it well suited to I/O-heavy servers?
Summary
Node.js is a JavaScript runtime built on Chrome's V8 engine and libuv. It runs your JavaScript on a single main thread with an event loop and non-blocking I/O, so one process can handle many concurrent connections that mostly wait on network, disk or databases.
Longer Answer
Instead of a thread per request, Node registers I/O operations with the OS (epoll/kqueue/IOCP via libuv) and runs callbacks when results are ready. That makes it efficient for APIs, real-time apps (WebSockets), proxies and streaming. CPU-heavy work blocks the event loop, so it needs worker threads, child processes or separate services.
Key Points
- V8 + libuv runtime
- Single-threaded event loop for JS
- Non-blocking I/O handles high concurrency
- CPU-bound work must be offloaded
-
Most asked Explain the phases of the Node.js event loop.
Summary
Each loop iteration runs these phases in order: timers (
setTimeout/setIntervalcallbacks), pending callbacks (some system errors), idle/prepare (internal), poll (retrieve new I/O events and run I/O callbacks; may block waiting), check (setImmediatecallbacks) and close callbacks (socket.on('close')).Longer Answer
Between each callback, Node drains the
process.nextTickqueue and then the Promise microtask queue. The loop exits when there are no more pending handles or requests. Understanding phases explains ordering puzzles, e.g.setImmediatealways runs beforesetTimeout(0)when both are scheduled from within an I/O callback.Code Example
const fs = require("node:fs"); fs.readFile(__filename, () => { setTimeout(() => console.log("timeout"), 0); setImmediate(() => console.log("immediate")); process.nextTick(() => console.log("nextTick")); Promise.resolve().then(() => console.log("promise")); }); // nextTick, promise, immediate, timeoutKey Points
- timers → pending → poll → check → close
- nextTick and microtasks drain between callbacks
- Poll phase waits for I/O
setImmediateruns in the check phase
Deep dive: Request Lifecycle course: WSGI, ASGI and the Event Loop
-
Most asked What is the difference between `process.nextTick`, Promise microtasks, `setImmediate` and `setTimeout(fn, 0)`?
Summary
process.nextTickcallbacks run immediately after the current operation, before Promise microtasks; Promise callbacks (then,awaitcontinuations) run next;setImmediateruns in the check phase of the loop;setTimeout(fn, 0)runs in the timers phase (at least about 1 ms later).Longer Answer
Recursive
nextTick(or endless microtasks) can starve I/O because the queues are drained completely before the loop continues. PreferqueueMicrotaskorsetImmediatefor deferring work. The order betweensetTimeout(0)andsetImmediatein the main module is non-deterministic.Key Points
- nextTick > microtasks > macrotasks
setImmediate: check phasesetTimeout(0): timers phase- Recursive nextTick can starve I/O
-
Most asked How has asynchronous code evolved in Node.js: callbacks, Promises and async/await?
Summary
Node started with error-first callbacks (
(err, data) => {}), moved to Promises (chainable, composable) and now favorsasync/awaiton top of Promises, with promise-based core APIs likefs/promises,timers/promisesandstream/promises.Longer Answer
Callbacks led to deeply nested 'callback hell' and inconsistent error handling. Promises allow
Promise.all,allSettled,raceandanyfor composition, andasync/awaitmakes async code read sequentially withtry/catch.util.promisifyconverts callback APIs. Always handle rejections; unhandled rejections crash the process by default since Node 15.Code Example
import { readFile } from "node:fs/promises"; async function loadConfigs(paths) { const results = await Promise.allSettled(paths.map((p) => readFile(p, "utf8"))); return results .filter((r) => r.status === "fulfilled") .map((r) => JSON.parse(r.value)); }Key Points
- Error-first callbacks (legacy)
- Promises for composition
async/awaitwith try/catch- Promise-based core modules
- Unhandled rejections crash the process
-
Most asked Why is CPU-bound work a problem in Node.js and how do you handle it?
Summary
JavaScript runs on one main thread, so a long synchronous computation (large JSON parsing, image processing, crypto, heavy loops) blocks the event loop: no other requests, timers or I/O callbacks run until it finishes.
Longer Answer
Solutions: move work to
worker_threads, spawn child processes, offload to a job queue or a service in another language, use async/native implementations (e.g.,crypto.scryptasync, sharp for images), or break work into chunks withsetImmediate. Monitor event loop lag (perf_hooks.monitorEventLoopDelay) to detect blocking.Key Points
- Blocks all concurrent requests
worker_threadsfor CPU tasks- Job queues or separate services
- Monitor event loop delay
-
Most asked What are streams in Node.js and what is backpressure?
Summary
Streams process data in chunks instead of loading it all into memory. There are Readable, Writable, Duplex and Transform streams. Backpressure is the mechanism that slows a fast producer when a slower consumer's buffer fills (
write()returnsfalseuntil'drain').Longer Answer
Use
stream.pipeline()(orawait pipeline()fromstream/promises) to connect streams with proper error propagation and cleanup; plain.pipe()doesn't forward errors. Async iteration (for await (const chunk of readable)) is a modern consumption style.highWaterMarkcontrols buffer sizes.Code Example
import { createReadStream, createWriteStream } from "node:fs"; import { createGzip } from "node:zlib"; import { pipeline } from "node:stream/promises"; await pipeline( createReadStream("access.log"), createGzip(), createWriteStream("access.log.gz"), );Key Points
- Chunked processing, constant memory
- Readable/Writable/Duplex/Transform
- Backpressure via
write()return +drain - Use
pipeline()for error handling
-
Most asked What are the differences between CommonJS and ES modules in Node.js?
Summary
CommonJS uses
require/module.exports, loads synchronously and is evaluated at runtime; ES modules useimport/export, are statically analyzable, load asynchronously, support top-levelawait, and are strict mode by default. Node picks the format from.mjs/.cjsextensions or thepackage.jsontypefield.Longer Answer
ESM can import CommonJS (default import =
module.exports). CommonJS can nowrequire()synchronous ESM graphs in modern Node (22.12+/20.19+), but not ones using top-level await. In ESM,__dirnameis replaced byimport.meta.dirname/import.meta.filename(Node 20.11+). Packages use theexportsfield to define entry points and conditional exports for both formats.Key Points
requirevsimporttype: moduleor.mjs/.cjs- Top-level await only in ESM
require(esm)supported in modern Nodeexportsfield for package entry points
-
Most asked How does middleware work in Express?
Summary
Middleware are functions
(req, res, next)executed in registration order; each can modifyreq/res, end the response, or callnext()to pass control. Error-handling middleware has four arguments(err, req, res, next)and is registered last.Longer Answer
Typical stack: security headers (helmet), CORS, body parsing (
express.json()), logging, auth, routes, 404 handler, error handler. Express 5 (stable since 2024) forwards rejected promises from async handlers to the error handler automatically; in Express 4 you needed wrappers.Code Example
import express from "express"; import helmet from "helmet"; const app = express(); app.use(helmet()); app.use(express.json({ limit: "1mb" })); app.get("/users/:id", async (req, res) => { const user = await users.find(req.params.id); // Express 5 forwards rejections if (!user) return res.status(404).json({ error: "Not found" }); res.json(user); }); app.use((err, req, res, next) => { req.log?.error(err); res.status(err.status ?? 500).json({ error: err.expose ? err.message : "Internal error" }); });Key Points
(req, res, next)chain in order- Error middleware has 4 params
- Order matters: parsers before routes
- Express 5 handles async errors
Deep dive: Request Lifecycle course: Express: The Middleware Stack and Error Handling
-
Most asked How do you implement graceful shutdown in a Node.js server?
Summary
On
SIGTERM/SIGINT, stop accepting new connections (server.close()), let in-flight requests finish (with a timeout), close keep-alive connections, stop consumers, close DB pools and other resources, flush logs, then exit.Longer Answer
In Kubernetes, fail the readiness probe first so traffic stops arriving, then drain.
server.closeIdleConnections()andcloseAllConnections()(Node 18.2+) help with keep-alive sockets. Always set a hard timeout so a stuck request doesn't block shutdown forever.Code Example
function shutdown(signal) { logger.info({ signal }, "shutting down"); server.close(async () => { await db.end(); await queue.close(); process.exit(0); }); server.closeIdleConnections(); setTimeout(() => process.exit(1), 10_000).unref(); } process.on("SIGTERM", shutdown); process.on("SIGINT", shutdown);Key Points
- Handle SIGTERM/SIGINT
server.close()+ drain- Close DB pools and consumers
- Hard timeout fallback
-
Most asked How do you work with relational databases efficiently in Node.js?
Summary
Use a connection pool (
pg.Pool, mysql2 pools) shared across requests, parameterized queries, transactions with proper release infinally, and an ORM or query builder (Prisma, Drizzle, Kysely, TypeORM, Sequelize) when it helps productivity.Longer Answer
Size pools against DB limits (pool size × instances), always release clients, avoid N+1 queries, and set statement timeouts. For serverless, use external poolers (PgBouncer, RDS Proxy) or HTTP-based drivers.
Code Example
import pg from "pg"; const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 10 }); export async function transfer(from, to, amount) { const client = await pool.connect(); try { await client.query("BEGIN"); await client.query("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [amount, from]); await client.query("UPDATE accounts SET balance = balance + $1 WHERE id = $2", [amount, to]); await client.query("COMMIT"); } catch (err) { await client.query("ROLLBACK"); throw err; } finally { client.release(); } }Key Points
- Shared connection pool
- Parameterized queries
- Release clients in
finally - Pool size vs DB max connections
-
Most asked Walk through how an Express app handles a request, including errors from async handlers.
Summary
Middleware and routes run in registration order; each either responds or calls next(). next(err), or a synchronous throw, skips to error-handling middleware, which Express recognises by its four parameters. If nothing responds, Express returns 404. Express 4 ignores rejected promises from async handlers (the request hangs and an unhandledRejection is logged); Express 5 forwards them to the error handlers.
Longer Answer
Register error handlers last, declare them with all four parameters, and return after sending a response to avoid 'Cannot set headers after they are sent'. In Express 4, wrap async handlers so rejections call next(err), or upgrade to Express 5.
Code Example
// Express 4: forward async errors explicitly const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next); app.get("/orders", wrap(async (req, res) => res.json(await db.orders()))); app.use((err, req, res, next) => { // four parameters = error handler res.status(500).json({ error: "internal", requestId: req.id }); });Key Points
- Registration order; next() continues.
- Error handlers have four parameters.
- Express 4 ignores rejected promises; Express 5 forwards them.
- Never respond twice.
Deep dive: Request Lifecycle course: Express: The Middleware Stack and Error Handling
-
Most asked What does libuv's thread pool do, and which operations use it?
Summary
libuv maintains a thread pool (default 4 threads, configurable with
UV_THREADPOOL_SIZEup to 1024) for operations that lack non-blocking OS APIs: mostfsoperations,dns.lookup, somecryptofunctions (pbkdf2, scrypt, randomBytes async) andzlibcompression.Longer Answer
Network I/O (TCP/HTTP sockets) doesn't use the pool; it uses OS event notification. If many slow fs or crypto calls saturate the 4 threads, other pool tasks queue up, causing surprising latency, e.g. DNS lookups delayed by bcrypt hashing. Increase the pool size or move heavy work elsewhere.
Key Points
- Default 4 threads (
UV_THREADPOOL_SIZE) - fs, dns.lookup, crypto, zlib
- Network sockets don't use it
- Saturation causes hidden latency
- Default 4 threads (
-
Most asked When do you use `Promise.all`, `Promise.allSettled`, `Promise.race` and `Promise.any`?
Summary
allwaits for every promise and rejects on the first failure;allSettledwaits for all and reports each outcome;racesettles with the first settled promise (fulfilled or rejected);anyfulfills with the first success and rejects only if all fail (AggregateError).Longer Answer
Use
allwhen every result is required,allSettledfor best-effort batches,racefor timeouts (or better,AbortSignal.timeout()), andanyfor redundant sources (fastest mirror). For large batches, limit concurrency (e.g.,p-limit) instead of firing thousands of requests at once.Key Points
all: all-or-nothingallSettled: every outcomerace: first settledany: first success- Limit concurrency for large batches
-
Most asked Compare `worker_threads`, `cluster` and `child_process`.
Summary
worker_threadsrun JavaScript in parallel threads within one process (with message passing and optional shared memory), good for CPU-bound tasks.clusterforks multiple processes sharing a server port to use all CPU cores for request handling.child_processspawns separate programs or Node scripts (spawn,exec,fork).Longer Answer
In containerized deployments, many teams skip
clusterand run one process per container, scaling with the orchestrator. Worker pools (e.g., Piscina) manage reusable workers. Workers have their own event loop and V8 isolate; transfer large buffers with transferables orSharedArrayBuffer.Code Example
// main.js import { Worker } from "node:worker_threads"; function runHash(data) { return new Promise((resolve, reject) => { const worker = new Worker(new URL("./hash-worker.js", import.meta.url), { workerData: data }); worker.once("message", resolve); worker.once("error", reject); }); } // hash-worker.js import { parentPort, workerData } from "node:worker_threads"; import { createHash } from "node:crypto"; parentPort.postMessage(createHash("sha256").update(workerData).digest("hex"));Key Points
- Workers: threads for CPU work
- Cluster: multi-process servers
- child_process: external programs
- Containers: often one process per container
-
Most asked How should errors be handled in a Node.js application?
Summary
Use
try/catchwithasync/await, always handle promise rejections, pass errors to framework error handlers (e.g., Expressnext(err)), distinguish operational errors (timeouts, invalid input) from programmer errors (bugs), and crash and restart on unknown states.Longer Answer
Process-level handlers (
process.on('uncaughtException'),'unhandledRejection') should log and exit gracefully, not keep running in a corrupted state; a supervisor (systemd, Kubernetes, PM2) restarts the process. UseErrorsubclasses with codes, andcauseto chain errors.Code Example
class NotFoundError extends Error { constructor(resource, options) { super(`${resource} not found`, options); this.name = "NotFoundError"; this.status = 404; } } process.on("unhandledRejection", (reason) => { logger.fatal({ reason }, "unhandled rejection"); process.exitCode = 1; shutdown(); });Key Points
- Operational vs programmer errors
- Never ignore rejections
- Log and exit on uncaught exceptions
- Custom error classes +
cause
-
Most asked What causes memory leaks in Node.js and how do you diagnose them?
Summary
Common causes: unbounded caches or global arrays, event listeners never removed, closures holding large objects, timers/intervals never cleared, and growing queues of unprocessed work. Diagnose with heap snapshots (Chrome DevTools via
--inspect,v8.writeHeapSnapshot()), comparing snapshots over time, and monitoringprocess.memoryUsage().Longer Answer
Use LRU caches with size limits,
WeakMapfor object metadata, remove listeners, and clear intervals on shutdown. Tools: clinic.js (heapprofiler),--heapsnapshot-near-heap-limit, APM memory metrics. Setting--max-old-space-sizeonly delays a real leak.Key Points
- Unbounded caches/listeners/timers
- Heap snapshot comparison
- LRU caches and WeakMaps
- Monitor RSS and heap usage
-
Most asked What are key security practices for Node.js web applications?
Summary
Validate all input (schemas), use parameterized queries, set security headers (helmet), apply rate limiting, keep dependencies patched (
npm audit, Dependabot), avoideval/child_process.execwith user input, protect against prototype pollution, store secrets in env/secret managers, and run as a non-root user.Longer Answer
Also: limit request body sizes, use HTTPS, configure CORS narrowly, hash passwords with bcrypt/argon2, set secure cookie flags, and avoid exposing stack traces. Node's permission model (
--permission) can restrict file system and child process access.Key Points
- Validate input, parameterize queries
- Helmet, CORS, rate limits
- Patch dependencies; audit
- No eval/exec with user input
- Least privilege runtime
-
Most asked How would you implement authentication in a Node.js API?
Summary
Choose sessions (server-side store + HttpOnly cookie) for browser apps or tokens (short-lived JWT access tokens + rotating refresh tokens) for APIs/mobile. Hash passwords with bcrypt/argon2, verify tokens with pinned algorithms, and enforce authorization per route and per resource.
Longer Answer
Libraries: Passport (strategies), Auth.js, Lucia-style session patterns, or identity providers (Auth0, Cognito, Keycloak). Rate-limit login, support MFA, and never store tokens in localStorage if XSS is a concern.
Key Points
- Sessions for browsers, tokens for APIs
- bcrypt/argon2 hashing
- Pin JWT algorithms, short expiry
- Authorize per resource
-
Most asked What makes a well-designed REST API in Node.js?
Summary
Resource-oriented URLs, correct HTTP methods and status codes, consistent JSON shapes and error format, pagination/filtering, validation, versioning, idempotency for retries, authentication/authorization, rate limiting and OpenAPI documentation.
Longer Answer
Return 201 + Location for creates, 204 for deletes, 409 for conflicts and 422 for validation errors. Use cursor pagination for large collections and ETags for caching/concurrency.
Key Points
- Resources + correct verbs/status codes
- Consistent errors (RFC 9457)
- Pagination, filtering, versioning
- OpenAPI docs
-
Most asked How do you implement rate limiting in a Node.js API?
Summary
Use middleware (express-rate-limit, @fastify/rate-limit) backed by Redis for multi-instance deployments, with algorithms like fixed window, sliding window or token bucket, keyed by API key, user ID or client IP, and return 429 with
Retry-After.Longer Answer
Configure
trust proxycorrectly soreq.ipis the real client IP behind load balancers. Apply stricter limits to auth endpoints, and add edge rate limiting (CDN/WAF) for volumetric attacks.Key Points
- Redis-backed shared limits
- Key by user/API key/IP
trust proxyfor real IPs- 429 +
Retry-After
-
Most asked How do you scale a Node.js application?
Summary
Scale vertically by using all cores (cluster/PM2 or multiple containers) and horizontally by running many stateless instances behind a load balancer, with shared state in Redis/databases, caching and CDNs, and background work in queues.
Longer Answer
Keep instances stateless (no in-memory sessions), externalize WebSocket fan-out via pub/sub, and autoscale on CPU, latency or event loop lag. Profile before scaling: fixing a blocking hot path can be worth more than adding instances.
Key Points
- Use all cores (processes/containers)
- Stateless instances + LB
- Shared state in Redis/DB
- Queues for background work
- What is a `Buffer` in Node.js?
- What is the `EventEmitter` and where is it used?
- Compare Express, Fastify, NestJS and Hono.
- How do you profile CPU performance in a Node.js app?
- What is prototype pollution and how do you prevent it?
- How should configuration and secrets be managed in Node.js?
- How do you create an HTTP server with Node's core `http` module, and why use a framework instead?
- How do you make HTTP requests in modern Node.js?
- How do `AbortController` and `AbortSignal` work in Node?
- How should you log in a production Node.js service?
- What is `AsyncLocalStorage` and how is it used?
- How do you test Node.js applications?
- Compare npm, pnpm and Yarn, and explain lockfiles.
- Explain semantic versioning ranges like `^1.2.3` and `~1.2.3`.
- How do you reduce npm supply-chain risk?
- How does Node.js's release schedule work, and which versions should production use?
- How do Prisma, Drizzle and Kysely differ?
- How do you use Redis with Node.js for caching and coordination?
- How do you run background jobs in Node.js?
- How do you validate request input in Node.js APIs?
- How do you handle large file uploads in Node.js?
- How do you build real-time features with WebSockets in Node.js?
- How does V8's garbage collector work, and how does it affect Node apps?
- What's the difference between `exec`, `execFile` and `spawn`, and what's the security concern?
- How do you prevent path traversal when serving files in Node.js?
- Which `node:crypto` APIs should you use for common tasks?
- What are best practices for running Node.js in Docker?
- How do Node.js microservices communicate reliably?
- How do you add observability to a Node.js service?
- What is NoSQL injection in Node/MongoDB apps and how do you prevent it?
- What happens to other requests when one request blocks the Node.js event loop?
- Which modern built-in Node.js features reduce the need for third-party tools?
- What is Node.js's permission model?
- How do Bun and Deno compare with Node.js?
- When would you use Server-Sent Events instead of WebSockets?
- How would you build a GraphQL API in Node.js and what pitfalls should you watch for?
- How do you schedule recurring jobs in a Node.js system?
- What are hidden classes and inline caches in V8, and why do they matter for performance?
- How do you size Node.js memory in containers?
- Should Node.js terminate TLS itself?
- What do `timer.unref()` and `ref()` do?
- How should a Node.js process exit, and why prefer `process.exitCode` over `process.exit()`?
- How do you debug a Node.js application?
- How does Node resolve packages, and what does the `exports` field do?
- What are dependencies, devDependencies and peerDependencies?
- What is PM2 and is it still needed with containers?
- What is a circuit breaker and how would you use one in Node.js?
- How do you design liveness and readiness checks for a Node service?
- Should a Node.js server compress responses itself?
- How can large JSON payloads hurt Node.js performance, and what are alternatives?
- Which Web-standard APIs are available globally in modern Node.js?
- How would you structure a medium-to-large Node.js backend codebase?
- How do you do dependency injection in Node.js without a framework?
- How do you make a Node.js payment or order endpoint idempotent?
- Which `node:util` functions are most useful?
- How do you build a CLI tool with Node.js?
- What are best practices for using the `fs` module?
- How does module caching work in Node.js?
- How do you handle dates and timezones correctly in Node.js?
- What does the Twelve-Factor App methodology recommend for Node services?
- What are Node.js Single Executable Applications (SEA)?
- What is `node:diagnostics_channel` used for?
- What is `node:sqlite`?
- How do you detect and fix N+1 queries in Node ORMs?
- What is the Backend-for-Frontend (BFF) pattern and why is Node popular for it?
- How do you format numbers, currencies and plurals for different locales in Node?
- How do you use MongoDB effectively from Node.js?
- How does Fastify's plugin system and encapsulation work?
- Describe NestJS's core building blocks.
- How do you organize routes in a larger Express app?
- How do sessions work with `express-session`, and what store should you use?
- Why does HTTP keep-alive matter for outbound requests in Node?
- Does Node.js support HTTP/2 and when would you use it directly?
- How do you consume Kafka events reliably in Node.js?
- How do you achieve zero-downtime deployments for Node services?
- How are feature flags used in Node.js services?
- What are the options for running TypeScript in a Node.js backend?
- How does the `cluster` module distribute connections, and why might you need sticky sessions?
- How do you implement HTTP caching in Node.js APIs?
- What tooling keeps a Node.js codebase healthy?
- How do you process a large log file line by line in Node.js?