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.

  1. 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

    Permalink

  2. Most asked Explain the phases of the Node.js event loop.

    Summary

    Each loop iteration runs these phases in order: timers (setTimeout/setInterval callbacks), pending callbacks (some system errors), idle/prepare (internal), poll (retrieve new I/O events and run I/O callbacks; may block waiting), check (setImmediate callbacks) and close callbacks (socket.on('close')).

    Longer Answer

    Between each callback, Node drains the process.nextTick queue 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. setImmediate always runs before setTimeout(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, timeout
    

    Key Points

    • timers → pending → poll → check → close
    • nextTick and microtasks drain between callbacks
    • Poll phase waits for I/O
    • setImmediate runs in the check phase

    Deep dive: Request Lifecycle course: WSGI, ASGI and the Event Loop

    Permalink

  3. Most asked What is the difference between `process.nextTick`, Promise microtasks, `setImmediate` and `setTimeout(fn, 0)`?

    Summary

    process.nextTick callbacks run immediately after the current operation, before Promise microtasks; Promise callbacks (then, await continuations) run next; setImmediate runs 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. Prefer queueMicrotask or setImmediate for deferring work. The order between setTimeout(0) and setImmediate in the main module is non-deterministic.

    Key Points

    • nextTick > microtasks > macrotasks
    • setImmediate: check phase
    • setTimeout(0): timers phase
    • Recursive nextTick can starve I/O

    Permalink

  4. 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 favors async/await on top of Promises, with promise-based core APIs like fs/promises, timers/promises and stream/promises.

    Longer Answer

    Callbacks led to deeply nested 'callback hell' and inconsistent error handling. Promises allow Promise.all, allSettled, race and any for composition, and async/await makes async code read sequentially with try/catch. util.promisify converts 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/await with try/catch
    • Promise-based core modules
    • Unhandled rejections crash the process

    Permalink

  5. 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.scrypt async, sharp for images), or break work into chunks with setImmediate. Monitor event loop lag (perf_hooks.monitorEventLoopDelay) to detect blocking.

    Key Points

    • Blocks all concurrent requests
    • worker_threads for CPU tasks
    • Job queues or separate services
    • Monitor event loop delay

    Permalink

  6. 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() returns false until 'drain').

    Longer Answer

    Use stream.pipeline() (or await pipeline() from stream/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. highWaterMark controls 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

    Permalink

  7. 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 use import/export, are statically analyzable, load asynchronously, support top-level await, and are strict mode by default. Node picks the format from .mjs/.cjs extensions or the package.json type field.

    Longer Answer

    ESM can import CommonJS (default import = module.exports). CommonJS can now require() synchronous ESM graphs in modern Node (22.12+/20.19+), but not ones using top-level await. In ESM, __dirname is replaced by import.meta.dirname/import.meta.filename (Node 20.11+). Packages use the exports field to define entry points and conditional exports for both formats.

    Key Points

    • require vs import
    • type: module or .mjs/.cjs
    • Top-level await only in ESM
    • require(esm) supported in modern Node
    • exports field for package entry points

    Permalink

  8. Most asked How does middleware work in Express?

    Summary

    Middleware are functions (req, res, next) executed in registration order; each can modify req/res, end the response, or call next() 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

    Permalink

  9. 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() and closeAllConnections() (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

    Permalink

  10. 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 in finally, 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

    Permalink

  11. 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

    Permalink

  12. 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_SIZE up to 1024) for operations that lack non-blocking OS APIs: most fs operations, dns.lookup, some crypto functions (pbkdf2, scrypt, randomBytes async) and zlib compression.

    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

    Permalink

  13. Most asked When do you use `Promise.all`, `Promise.allSettled`, `Promise.race` and `Promise.any`?

    Summary

    all waits for every promise and rejects on the first failure; allSettled waits for all and reports each outcome; race settles with the first settled promise (fulfilled or rejected); any fulfills with the first success and rejects only if all fail (AggregateError).

    Longer Answer

    Use all when every result is required, allSettled for best-effort batches, race for timeouts (or better, AbortSignal.timeout()), and any for 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-nothing
    • allSettled: every outcome
    • race: first settled
    • any: first success
    • Limit concurrency for large batches

    Permalink

  14. Most asked Compare `worker_threads`, `cluster` and `child_process`.

    Summary

    worker_threads run JavaScript in parallel threads within one process (with message passing and optional shared memory), good for CPU-bound tasks. cluster forks multiple processes sharing a server port to use all CPU cores for request handling. child_process spawns separate programs or Node scripts (spawn, exec, fork).

    Longer Answer

    In containerized deployments, many teams skip cluster and 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 or SharedArrayBuffer.

    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

    Permalink

  15. Most asked How should errors be handled in a Node.js application?

    Summary

    Use try/catch with async/await, always handle promise rejections, pass errors to framework error handlers (e.g., Express next(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. Use Error subclasses with codes, and cause to 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

    Permalink

  16. 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 monitoring process.memoryUsage().

    Longer Answer

    Use LRU caches with size limits, WeakMap for object metadata, remove listeners, and clear intervals on shutdown. Tools: clinic.js (heapprofiler), --heapsnapshot-near-heap-limit, APM memory metrics. Setting --max-old-space-size only delays a real leak.

    Key Points

    • Unbounded caches/listeners/timers
    • Heap snapshot comparison
    • LRU caches and WeakMaps
    • Monitor RSS and heap usage

    Permalink

  17. 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), avoid eval/child_process.exec with 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

    Permalink

  18. 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

    Permalink

  19. 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

    Permalink

  20. 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 proxy correctly so req.ip is 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 proxy for real IPs
    • 429 + Retry-After

    Permalink

  21. 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

    Permalink

  22. What is a `Buffer` in Node.js?
  23. What is the `EventEmitter` and where is it used?
  24. Compare Express, Fastify, NestJS and Hono.
  25. How do you profile CPU performance in a Node.js app?
  26. What is prototype pollution and how do you prevent it?
  27. How should configuration and secrets be managed in Node.js?
  28. How do you create an HTTP server with Node's core `http` module, and why use a framework instead?
  29. How do you make HTTP requests in modern Node.js?
  30. How do `AbortController` and `AbortSignal` work in Node?
  31. How should you log in a production Node.js service?
  32. What is `AsyncLocalStorage` and how is it used?
  33. How do you test Node.js applications?
  34. Compare npm, pnpm and Yarn, and explain lockfiles.
  35. Explain semantic versioning ranges like `^1.2.3` and `~1.2.3`.
  36. How do you reduce npm supply-chain risk?
  37. How does Node.js's release schedule work, and which versions should production use?
  38. How do Prisma, Drizzle and Kysely differ?
  39. How do you use Redis with Node.js for caching and coordination?
  40. How do you run background jobs in Node.js?
  41. How do you validate request input in Node.js APIs?
  42. How do you handle large file uploads in Node.js?
  43. How do you build real-time features with WebSockets in Node.js?
  44. How does V8's garbage collector work, and how does it affect Node apps?
  45. What's the difference between `exec`, `execFile` and `spawn`, and what's the security concern?
  46. How do you prevent path traversal when serving files in Node.js?
  47. Which `node:crypto` APIs should you use for common tasks?
  48. What are best practices for running Node.js in Docker?
  49. How do Node.js microservices communicate reliably?
  50. How do you add observability to a Node.js service?
  51. What is NoSQL injection in Node/MongoDB apps and how do you prevent it?
  52. What happens to other requests when one request blocks the Node.js event loop?
  53. Which modern built-in Node.js features reduce the need for third-party tools?
  54. What is Node.js's permission model?
  55. How do Bun and Deno compare with Node.js?
  56. When would you use Server-Sent Events instead of WebSockets?
  57. How would you build a GraphQL API in Node.js and what pitfalls should you watch for?
  58. How do you schedule recurring jobs in a Node.js system?
  59. What are hidden classes and inline caches in V8, and why do they matter for performance?
  60. How do you size Node.js memory in containers?
  61. Should Node.js terminate TLS itself?
  62. What do `timer.unref()` and `ref()` do?
  63. How should a Node.js process exit, and why prefer `process.exitCode` over `process.exit()`?
  64. How do you debug a Node.js application?
  65. How does Node resolve packages, and what does the `exports` field do?
  66. What are dependencies, devDependencies and peerDependencies?
  67. What is PM2 and is it still needed with containers?
  68. What is a circuit breaker and how would you use one in Node.js?
  69. How do you design liveness and readiness checks for a Node service?
  70. Should a Node.js server compress responses itself?
  71. How can large JSON payloads hurt Node.js performance, and what are alternatives?
  72. Which Web-standard APIs are available globally in modern Node.js?
  73. How would you structure a medium-to-large Node.js backend codebase?
  74. How do you do dependency injection in Node.js without a framework?
  75. How do you make a Node.js payment or order endpoint idempotent?
  76. Which `node:util` functions are most useful?
  77. How do you build a CLI tool with Node.js?
  78. What are best practices for using the `fs` module?
  79. How does module caching work in Node.js?
  80. How do you handle dates and timezones correctly in Node.js?
  81. What does the Twelve-Factor App methodology recommend for Node services?
  82. What are Node.js Single Executable Applications (SEA)?
  83. What is `node:diagnostics_channel` used for?
  84. What is `node:sqlite`?
  85. How do you detect and fix N+1 queries in Node ORMs?
  86. What is the Backend-for-Frontend (BFF) pattern and why is Node popular for it?
  87. How do you format numbers, currencies and plurals for different locales in Node?
  88. How do you use MongoDB effectively from Node.js?
  89. How does Fastify's plugin system and encapsulation work?
  90. Describe NestJS's core building blocks.
  91. How do you organize routes in a larger Express app?
  92. How do sessions work with `express-session`, and what store should you use?
  93. Why does HTTP keep-alive matter for outbound requests in Node?
  94. Does Node.js support HTTP/2 and when would you use it directly?
  95. How do you consume Kafka events reliably in Node.js?
  96. How do you achieve zero-downtime deployments for Node services?
  97. How are feature flags used in Node.js services?
  98. What are the options for running TypeScript in a Node.js backend?
  99. How does the `cluster` module distribute connections, and why might you need sticky sessions?
  100. How do you implement HTTP caching in Node.js APIs?
  101. What tooling keeps a Node.js codebase healthy?
  102. How do you process a large log file line by line in Node.js?