Top 62 Node.js Interview Questions and Answers (2026)

The 62 Node.js questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced backend rounds.

62 questions with answers

What Is Node.js?

Key Takeaways

  • Node.js is a runtime that runs JavaScript outside the browser on Chrome's V8 engine, built for I/O-heavy servers and tools.
  • Its model is single-threaded and event-driven: one thread runs your JavaScript, while libuv handles I/O in the background and reports back through the event loop.
  • Interviews test whether you understand the event loop, non-blocking I/O, and streams, not whether you can list npm commands.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery is evaluated too.

Node.js is a JavaScript runtime built on Chrome's V8 engine that runs JavaScript on the server, released by Ryan Dahl in 2009 and now stewarded by the OpenJS Foundation. It pairs V8 with libuv, a C library that provides the event loop and a thread pool, so a single JavaScript thread can juggle thousands of concurrent connections without a thread per request. That design makes Node.js a strong fit for APIs, real-time services, and streaming, and a weaker fit for CPU-bound number crunching. In interviews, Node.js questions probe the runtime model (the event loop, non-blocking I/O, the thread pool) and backend judgment (error handling, streams, security, production behavior), not memorized syntax. This page collects the 62 questions that come up most, each with a direct answer and runnable code. If you're still building fundamentals, the OpenJS Foundation's Introduction to Node.js is the canonical starting point; increasingly the first backend round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

62Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
24Runnable code snippets you can practice from
45-60 minTypical length of a Node.js backend round

Watch: Node.js Crash Course

Video: Node.js Crash Course (Traversy Media, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Node.js certificate.

Jump to quiz

All Questions on This Page

62 questions
Node.js Interview Questions for Freshers
  1. 1. What is Node.js and why would you use it?
  2. 2. Is Node.js single-threaded? How does one thread handle many requests?
  3. 3. What is the event loop and what are its phases?
  4. 4. What is the difference between blocking and non-blocking code in Node.js?
  5. 5. What is the difference between CommonJS and ES modules?
  6. 6. What are npm, package.json, and package-lock.json?
  7. 7. How do callbacks, Promises, and async/await relate?
  8. 8. What is callback hell and how do you avoid it?
  9. 9. What is the difference between npm and npx?
  10. 10. What happens the second time you require the same module?
  11. 11. What are __dirname, __filename, and the global object?
  12. 12. What is the process object and what do you use it for?
  13. 13. How do you manage environment variables and configuration?
  14. 14. How do you create a basic HTTP server without a framework?
  15. 15. What is Express and why is it so widely used?
  16. 16. What is middleware in Express?
  17. 17. How does routing work in Express?
  18. 18. What are the request and response objects in Express?
  19. 19. Why are there both fs.readFile and fs.readFileSync?
  20. 20. What is a Buffer in Node.js?
  21. 21. How do you parse and stringify JSON in Node.js?
  22. 22. What is the error-first callback convention?
  23. 23. What are npm scripts and tools like nodemon for?
  24. 24. Which HTTP status codes should a Node.js API return, and when?
Node.js Intermediate Interview Questions
  1. 25. What is libuv and what does its thread pool do?
  2. 26. What is the difference between process.nextTick, setImmediate, and Promises?
  3. 27. What are streams and what are the four types?
  4. 28. What is backpressure and how do pipe and pipeline handle it?
  5. 29. How does the EventEmitter pattern work?
  6. 30. How do you handle errors across callbacks, Promises, and async/await?
  7. 31. What happens on an uncaught exception or unhandled promise rejection?
  8. 32. What is the cluster module and when do you use it?
  9. 33. What are worker_threads and how do they differ from cluster?
  10. 34. How do you run external commands or scripts with child_process?
  11. 35. What is the difference between session-based and JWT authentication?
  12. 36. What are the essential security practices for a Node.js API?
  13. 37. What is CORS and how do you handle it in Node.js?
  14. 38. What makes a well-designed REST API?
  15. 39. Why does middleware order matter in Express?
  16. 40. How do you connect to a database and manage connections in Node.js?
  17. 41. How do you add timeouts and cancellation to outbound requests?
  18. 42. How do you run async operations in parallel versus in sequence?
  19. 43. How do you test a Node.js application?
  20. 44. How do you debug and inspect a running Node.js process?
  21. 45. What is graceful shutdown and how do you implement it?
Node.js Interview Questions for Experienced Developers
  1. 46. What actually happens when V8 runs your JavaScript?
  2. 47. How do you diagnose and fix a memory leak in Node.js?
  3. 48. What is event loop lag, and how do you detect and prevent it?
  4. 49. How do you build a custom Transform stream, and why would you?
  5. 50. How would you design communication between Node.js microservices?
  6. 51. What caching strategies do you use in a Node.js service?
  7. 52. How do you scale a Node.js application horizontally?
  8. 53. How do you implement health checks and readiness probes?
  9. 54. How do you make a Node.js service observable in production?
  10. 55. Why does connection pooling matter, and how do you size a pool?
  11. 56. What is the N+1 query problem and how do you fix it in Node.js?
  12. 57. What are the real-world friction points between ESM and CommonJS?
  13. 58. How do async iterators relate to streams, and when do you prefer them?
  14. 59. Design question: how would you implement a middleware chain from scratch?
  15. 60. Design question: how would you build a distributed rate limiter in Node.js?
  16. 61. How do you add TypeScript to a Node.js project, and what does it buy you?
  17. 62. A Node.js service is slow or crashing in production. Walk through your approach.

Node.js Interview Questions for Freshers

Freshers24 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is Node.js and why would you use it?

Node.js is a runtime that runs JavaScript outside the browser, built on Chrome's V8 engine and paired with libuv for I/O. It lets you write servers, CLIs, and build tools in JavaScript, so one language covers the whole stack.

You reach for it when the work is I/O-bound: APIs, real-time apps, streaming, and services that hold many concurrent connections. Its non-blocking, event-driven model handles thousands of idle connections cheaply, which is exactly what those workloads need.

Key point: A one-line definition plus one concrete use case (an API or a real-time app) beats a feature list. Interviewers open with this to hear how you frame an answer.

Watch a deeper explanation

Video: Node.js Ultimate Beginner's Guide in 7 Easy Steps (Fireship, YouTube)

Q2. Is Node.js single-threaded? How does one thread handle many requests?

Your JavaScript runs on a single main thread. Node.js isn't fully single-threaded though: libuv maintains a background thread pool, and V8 uses threads for garbage collection and compilation. What matters is that your code, your callbacks, all run on one thread.

One thread handles many requests because most request work is waiting on I/O (database, network, disk), and Node.js doesn't block on that wait. It registers a callback, moves on to other requests, and the event loop resumes each callback when its I/O finishes. The thread is only busy during the tiny slices of actual computation.

Key point: The trap is saying Node.js is 'single-threaded' flatly. Nuance (your JS is single-threaded, libuv is not) is exactly what this screens for.

Q3. What is the event loop and what are its phases?

The event loop is the mechanism that lets a single thread do non-blocking I/O: it repeatedly checks queues of completed operations and runs their callbacks. libuv runs it, cycling through ordered phases.

Each full turn runs phases in order: timers (setTimeout, setInterval), pending callbacks, poll (retrieve new I/O and run I/O callbacks), check (setImmediate), and close callbacks. Between every phase, and after each callback, Node.js drains the microtask queues: process.nextTick first, then Promise callbacks.

One turn of the Node.js event loop

1timers
run setTimeout / setInterval callbacks whose time has elapsed
2poll
retrieve new I/O events and run most I/O callbacks
3check
run setImmediate callbacks
4close
run close-event callbacks, then loop again

After each callback and between phases, microtasks drain: process.nextTick first, then resolved Promises.

Key point: Knowing the phase order and that microtasks drain between them is the senior-sounding detail. Most candidates stop at 'it handles callbacks'.

Q4. What is the difference between blocking and non-blocking code in Node.js?

Blocking code stops the single thread until the operation finishes: readFileSync reads the whole file before the next line runs, and while it does, no other request gets served. Non-blocking code hands the operation to libuv, registers a callback, and returns immediately, so the thread stays free.

This is the whole point of Node.js. One synchronous, slow operation on the main thread stalls every other request. Prefer the async APIs, and keep CPU-heavy work off the main thread.

javascript
const fs = require("fs");

// Blocking: nothing else runs until the read finishes
const data = fs.readFileSync("big.log", "utf8");
console.log(data.length);

// Non-blocking: the thread is free while libuv reads
fs.readFile("big.log", "utf8", (err, data) => {
  if (err) throw err;
  console.log(data.length);
});

Key point: Have a concrete example ready (readFileSync vs readFile). Naming the sync API you'd avoid shows you've felt the pain.

Watch a deeper explanation

Video: Async JS Crash Course - Callbacks, Promises, Async Await (Traversy Media, YouTube)

Q5. What is the difference between CommonJS and ES modules?

CommonJS is Node.js's original module system: require() to import, module.exports to export, synchronous and dynamic, the default for .js files. ES modules (ESM) are the JavaScript standard: import and export, statically analyzed, asynchronous, with top-level await support.

You opt into ESM with a .mjs extension or "type": "module" in package.json. The practical friction: you can't require() an ESM-only package from CommonJS directly, which is behind a lot of 'ERR_REQUIRE_ESM' confusion.

javascript
// CommonJS
const express = require("express");
module.exports = { start };

// ES modules
import express from "express";
export function start() {}

Key point: Say why the choice matters (top-level await, static analysis) rather than just listing syntax. The follow-up is usually 'why can't CommonJS require an ESM-only package?'.

Q6. What are npm, package.json, and package-lock.json?

npm is Node's package manager. package.json declares your project's metadata, scripts, and dependency ranges (like "express": "^4.18.0"). package-lock.json records the exact resolved version of every package in the whole tree, direct and transitive.

The lock file is what makes installs reproducible: two machines running npm install off the same lock get byte-for-byte the same node_modules. Commit it. Use npm ci in CI, which installs strictly from the lock and fails if it's out of sync.

Key point: npm ci and 'commit the lock file' matters.

Q7. How do callbacks, Promises, and async/await relate?

They're three generations of handling async results. Callbacks came first: pass a function that runs when the work finishes, by convention (err, result). Promises wrap that into an object you can chain with .then and .catch, which flattens nesting. async/await is syntax over Promises: await pauses the function until the Promise settles, so async code reads top to bottom.

Modern Node.js code uses async/await with try/catch. Callbacks still appear in older APIs and event emitters, and util.promisify converts a callback API to a Promise one.

javascript
const fs = require("fs/promises");

async function loadConfig(path) {
  try {
    const text = await fs.readFile(path, "utf8");
    return JSON.parse(text);
  } catch (err) {
    console.error("config failed:", err.message);
    return {};
  }
}

Key point: the key point is that async/await is Promises underneath, not a separate thing. Saying 'await unwraps a Promise' shows you understand the mechanism.

Q8. What is callback hell and how do you avoid it?

Callback hell is deeply nested callbacks, each depending on the previous result, drifting rightward into a pyramid that's hard to read and error-prone because every level needs its own error check.

Fix it with Promises and async/await: chained .then calls flatten the pyramid, and await makes sequential async steps read like normal code. Named functions and Promise.all for independent work also help.

javascript
// pyramid of doom
getUser(id, (e, user) => {
  getOrders(user, (e, orders) => {
    getTotals(orders, (e, totals) => { /* ... */ });
  });
});

// flattened with async/await
const user = await getUser(id);
const orders = await getOrders(user);
const totals = await getTotals(orders);

Key point: Don't just say 'use async/await'. Point out that each callback level also needed its own error check, which is the real cost async/await removes.

Q9. What is the difference between npm and npx?

npm installs and manages packages. npx executes a package's binary, downloading it temporarily if it isn't installed, so you can run a one-off tool (npx create-react-app) without a global install that lingers.

The everyday value is running the locally installed version of a CLI (npx eslint) rather than a possibly-different global one, which keeps behavior consistent with what your project depends on.

Key point: A crisp one-liner works here: npm installs, npx runs. Then add the local-version detail if they push for more.

Q10. What happens the second time you require the same module?

Node.js caches modules after the first require. The first time, it runs the module's top-level code once and stores the resulting exports; every later require of the same resolved path returns that cached exports object without re-running the file.

This is why module-level state (a counter, a database connection) is shared across every file that imports it, effectively a singleton. It's also why a change to a module's top-level side effects only runs once per process, a fact that surprises people debugging import-time behavior.

javascript
// counter.js
let count = 0;
module.exports = () => ++count;

// a.js and b.js both require("./counter")
// they share ONE counter because the module is cached
const bump = require("./counter");
bump(); // 1
bump(); // 2

Key point: The module cache is the answer to 'how do you make a singleton in Node?'. it in practice matters.

Q11. What are __dirname, __filename, and the global object?

In CommonJS, __dirname is the absolute path of the current module's directory and __filename its full path, both handy for building paths that don't depend on the working directory. global is Node's top-level object, the server-side equivalent of the browser's window.

In ES modules, __dirname and __filename don't exist; you derive them from import.meta.url. Reaching for path.join(__dirname, ...) instead of string concatenation is the detail interviewers like to see.

javascript
const path = require("path");
const configPath = path.join(__dirname, "config", "app.json");

// ES modules equivalent
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);

Q12. What is the process object and what do you use it for?

process is a global that represents the running Node.js process. You use it to read command-line arguments (process.argv), environment variables (process.env), the current working directory (process.cwd()), and to control exit (process.exit()).

It's also an EventEmitter for lifecycle signals: 'exit', 'SIGTERM', 'uncaughtException'. Production services listen on these for graceful shutdown, which comes up in advanced rounds.

javascript
const port = process.env.PORT || 3000;
const [, , command] = process.argv;

process.on("SIGTERM", () => {
  console.log("shutting down");
  process.exit(0);
});

Key point: process is an EventEmitter (for SIGTERM).

Q13. How do you manage environment variables and configuration?

Read deployment-specific values from process.env, set by the platform. In development, a .env file loaded by the dotenv package (or Node's built-in --env-file flag) keeps local values out of the code, and you never commit that file.

The production discipline is: validate the variables you need once at startup, fail loudly if a required one is missing, and keep secrets in the platform's secret manager rather than the repo.

javascript
require("dotenv").config();

const { DATABASE_URL, JWT_SECRET } = process.env;
if (!DATABASE_URL || !JWT_SECRET) {
  throw new Error("missing required env vars");
}

Q14. How do you create a basic HTTP server without a framework?

The built-in http module's createServer takes a handler that receives the request and response objects for every request. You set the status and headers on the response and end it with the body.

Frameworks like Express wrap this to add routing, middleware, and conveniences, but knowing the raw version shows you understand what Express is doing underneath.

javascript
const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ ok: true, path: req.url }));
});

server.listen(3000, () => console.log("listening on 3000"));

Key point: Being able to write the raw http server is a quiet flex: it shows Express isn't magic to you. Interviewers sometimes ask exactly this to see if you know the layer beneath the framework.

Q15. What is Express and why is it so widely used?

Express is a minimal web framework for Node.js. It adds routing, a middleware pipeline, and request/response helpers on top of the http module, without dictating structure, which is why so much of the ecosystem is built around it.

Its staying power is that middleware model: request handling is a chain of small functions, each able to inspect, modify, respond, or pass control on. That pattern is easy to reason about and extend.

Q16. What is middleware in Express?

Middleware is a function with access to the request, the response, and a next function. It runs in the order registered, and it either responds, or calls next() to pass control to the next middleware in the chain. Logging, parsing bodies, authentication, and validation are all middleware.

The mental model is a pipeline: a request flows through each middleware until one sends a response. Forgetting to call next() (and not responding) is the classic bug that hangs a request.

javascript
const express = require("express");
const app = express();

app.use(express.json());                 // built-in: parse JSON bodies
app.use((req, res, next) => {             // custom logger
  console.log(`${req.method} ${req.url}`);
  next();                                 // pass control on
});

Key point: The follow-up is 'what happens if you forget next()?'. Answer: the request hangs. Volunteering it shows real Express experience.

Watch a deeper explanation

Video: Learn Express Middleware In 14 Minutes (Web Dev Simplified, YouTube)

Q17. How does routing work in Express?

You map an HTTP method and a path to a handler: app.get("/users/:id", handler). Route parameters (:id) land in req.params, query strings in req.query, and the parsed body in req.body. Handlers send a response with res.json, res.send, or res.status.

For larger apps, express.Router() groups related routes into a module you mount under a prefix, which keeps the main file small and the API organized.

javascript
const router = express.Router();

router.get("/:id", (req, res) => {
  res.json({ id: req.params.id });
});

app.use("/users", router);   // GET /users/42 -> { id: "42" }

Watch a deeper explanation

Video: Learn Express JS In 35 Minutes (Web Dev Simplified, YouTube)

Q18. What are the request and response objects in Express?

req represents the incoming HTTP request: req.params (route params), req.query (query string), req.body (parsed body), req.headers, and req.method. res is the outgoing response: res.status() sets the code, res.json() and res.send() write the body and end the response, res.set() sets headers.

The rule that trips people up: a response can only be sent once. Calling res.json() twice, or sending after next(), throws 'headers already sent'.

Q19. Why are there both fs.readFile and fs.readFileSync?

fs.readFileSync blocks the thread until the file is read; fs.readFile hands the read to libuv and calls back when done; fs.promises.readFile does the same but returns a Promise you can await. Same operation, three concurrency behaviors.

In a server, use the async versions so one file read doesn't stall every other request. The sync versions are fine at startup (loading config once) where nothing else is happening yet.

Q20. What is a Buffer in Node.js?

A Buffer is a fixed-length chunk of raw binary data outside V8's heap. Node.js uses buffers for anything that isn't text: file contents, network packets, image bytes, crypto. Streams deliver their chunks as buffers by default.

You convert between buffers and strings with an explicit encoding: buf.toString("utf8") or Buffer.from(str, "utf8"). Getting the encoding wrong is a common source of mojibake in file and network code.

javascript
const buf = Buffer.from("héllo", "utf8");
console.log(buf.length);        // 6 bytes, not 5 chars
console.log(buf.toString("hex"));
console.log(buf.toString("utf8")); // "héllo"

Key point: The bytes-vs-characters distinction (buf.length counts bytes, not characters) is the detail interviewers probe. The héllo example makes it land.

Q21. How do you parse and stringify JSON in Node.js?

JSON.parse turns a JSON string into JavaScript objects; JSON.stringify does the reverse. In an API, express.json() middleware parses request bodies into req.body for you, and res.json() stringifies your response.

The gotcha worth naming: JSON.parse throws on invalid input, so parsing untrusted data belongs in a try/catch. And JSON has no date type, so dates round-trip as strings unless you handle them.

Q22. What is the error-first callback convention?

Node.js's classic async APIs pass the error as the first argument to the callback and the result second: callback(err, data). You check err before touching data, every time. If err is null, the operation succeeded.

This convention predates Promises and still shows up in older libraries and stream events. Modern code usually promisifies these, but the question expects you to recognize and handle the pattern.

javascript
fs.readFile("data.txt", "utf8", (err, data) => {
  if (err) {
    console.error("read failed:", err.message);
    return;              // always return after handling err
  }
  console.log(data);
});

Q23. What are npm scripts and tools like nodemon for?

The scripts section of package.json names shell commands you run with npm run <name>: start, test, build, lint. It's the standard entry point so teammates don't memorize long commands.

nodemon is a development tool that watches your files and restarts the server on every change, so you don't stop and start Node by hand. It's a dev dependency, not something you run in production.

Q24. Which HTTP status codes should a Node.js API return, and when?

The everyday set: 200 for a successful GET, 201 for a created resource, 204 for a successful request with no body, 400 for a malformed request, 401 for missing or bad authentication, 403 for authenticated-but-forbidden, 404 for not found, 409 for a conflict, 422 for validation failure, and 500 for a server error.

The distinction interviewers probe most is 401 versus 403 (who you are versus what you're allowed to do) and 400 versus 500 (the client's fault versus the server's). Getting those right signals API maturity.

Key point: Nail 401 vs 403 and 400 vs 500 out loud. Those two distinctions are the whole reason this question gets asked.

Back to question list

Node.js Intermediate Interview Questions

Intermediate21 questions

For candidates with working experience: runtime mechanics, streams, and the backend judgment that separates users from understanders.

Q25. What is libuv and what does its thread pool do?

libuv is the C library that gives Node.js its event loop and cross-platform async I/O. For operations the OS exposes asynchronously (network sockets), libuv uses the kernel directly. For operations without a good async primitive (file system, DNS lookups, some crypto), it offloads to a thread pool so the main thread never blocks.

The pool defaults to 4 threads, tunable with UV_THREADPOOL_SIZE. It matters because heavy fs or crypto work can saturate those 4 threads and queue behind each other, a subtle bottleneck that looks like the event loop is slow when it's actually the pool.

Operation typeHandled byWhy
Network I/O (sockets)OS async primitivesKernel provides non-blocking sockets directly
File system, DNSlibuv thread poolNo portable async OS primitive, so offload to threads
crypto.pbkdf2, zliblibuv thread poolCPU-heavy but off the main thread
Your JavaScriptMain thread (V8)Single-threaded execution of your code

Key point: Knowing that fs and DNS use the pool (not the kernel) and that the default is 4 is the detail that separates memorized answers from understanding.

Q26. What is the difference between process.nextTick, setImmediate, and Promises?

process.nextTick queues a callback to run right after the current operation, before the event loop continues to any phase, and it drains fully before I/O. Promise callbacks (microtasks) run in the same slot, just after nextTick. setImmediate schedules a callback for the check phase of the next loop iteration, after I/O.

The priority order within one turn: current code, then all nextTick callbacks, then all Promise microtasks, then the loop proceeds. Overusing process.nextTick can starve I/O because it keeps jumping the queue.

javascript
setImmediate(() => console.log("immediate"));
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
console.log("sync");
// order: sync, nextTick, promise, immediate

Key point: Being able to predict the output order of this exact snippet is a common live check. Practice it until it's automatic.

Q27. What are streams and what are the four types?

Streams process data in chunks over time instead of loading it all into memory. The four types: Readable (a source, like fs.createReadStream or an HTTP request), Writable (a destination, like fs.createWriteStream or an HTTP response), Duplex (both, like a TCP socket), and Transform (a Duplex that modifies data passing through, like zlib compression).

The payoff is memory: streaming a 2 GB file uses kilobytes at a time, while readFile loads all 2 GB. Streams are also composable, you pipe a readable through transforms into a writable.

javascript
const fs = require("fs");
const zlib = require("zlib");

fs.createReadStream("input.txt")      // Readable
  .pipe(zlib.createGzip())            // Transform
  .pipe(fs.createWriteStream("input.txt.gz")); // Writable

Key point: Lead with the memory payoff (kilobytes at a time, not the whole file). Naming the four types is table stakes; explaining why you'd stream is the signal.

Q28. What is backpressure and how do pipe and pipeline handle it?

Backpressure is what happens when a readable produces data faster than a writable can consume it. Without control, the unconsumed chunks pile up in memory until the process runs out. The fix is to pause the readable when the writable's internal buffer is full, and resume when it drains.

pipe() handles this automatically: it watches the writable's return value and pauses the source. The problem is pipe() doesn't propagate errors well, so a failure can leak the stream. pipeline() (from stream/promises) is the modern choice: same backpressure handling plus proper error propagation and cleanup.

javascript
const { pipeline } = require("stream/promises");
const fs = require("fs");
const zlib = require("zlib");

await pipeline(
  fs.createReadStream("big.log"),
  zlib.createGzip(),
  fs.createWriteStream("big.log.gz")
);   // errors reject the promise; streams are cleaned up

Key point: Volunteering that you prefer pipeline over pipe 'because pipe leaks streams on error' is a strong production signal.

Q29. How does the EventEmitter pattern work?

EventEmitter is Node's implementation of the observer pattern: an object emits named events with .emit(name, ...args), and listeners registered with .on(name, handler) run synchronously in registration order when that event fires. Streams, the HTTP server, and the process object are all emitters.

Two things to know: emit is synchronous, so a slow listener blocks the emitter; and an 'error' event with no listener throws and crashes the process, so you always attach an 'error' handler.

javascript
const EventEmitter = require("events");

class Job extends EventEmitter {}
const job = new Job();

job.on("done", (result) => console.log("finished:", result));
job.on("error", (err) => console.error(err.message)); // always
job.emit("done", 42);

Key point: The 'error' event with no listener crashing the process is the follow-up interviewers wait for. Mention it before they ask.

Q30. How do you handle errors across callbacks, Promises, and async/await?

Each style has its own mechanism. Callbacks: check the error-first argument. Promises: .catch() at the end of the chain. async/await: try/catch around the await, or a .catch on the returned Promise. The mistake is mixing them, like putting a synchronous try/catch around a callback API, where it catches nothing.

In Express, an error thrown in an async route won't reach your error middleware unless you catch it and call next(err), or wrap handlers so rejections forward automatically. That gap is a frequent source of silently hanging requests.

javascript
// wrapper so async route errors reach error middleware
const wrap = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get("/users/:id", wrap(async (req, res) => {
  const user = await db.find(req.params.id);   // rejection -> next(err)
  res.json(user);
}));

Key point: The async-route-to-error-middleware gap is a favorite intermediate question. Knowing the wrap pattern (or that Express 5 handles it) sets you apart.

Q31. What happens on an uncaught exception or unhandled promise rejection?

An uncaught synchronous exception fires process 'uncaughtException'; an unhandled rejected Promise fires 'unhandledRejection'. Since Node 15, an unhandled rejection crashes the process by default, matching uncaught exceptions.

The correct posture: don't use these handlers to keep running as if nothing happened, because the process is now in an unknown state. Log the error, alert, and let the process exit so a supervisor restarts it clean. Recover state through your infrastructure, not by swallowing crashes.

javascript
process.on("unhandledRejection", (reason) => {
  console.error("unhandled rejection:", reason);
  process.exit(1);   // log, then exit; let the supervisor restart
});

Q32. What is the cluster module and when do you use it?

cluster forks the process into multiple workers that share the same server port, so a machine with 8 cores can run 8 Node processes instead of leaving 7 idle. The primary process distributes incoming connections across workers.

Use it to scale a stateless HTTP server across CPU cores on one box. Each worker is a separate process with its own memory, so shared state has to live elsewhere (Redis, a database). In containerized setups, running one process per container and scaling containers often replaces cluster.

Key point: Add the 'workers don't share memory' point and the container alternative. It shows you know cluster's limits, not just that it exists.

Q33. What are worker_threads and how do they differ from cluster?

worker_threads run JavaScript on real OS threads inside one process, sharing memory through SharedArrayBuffer and passing messages otherwise. They exist for CPU-bound work: image processing, parsing, cryptography, computation that would otherwise block the event loop.

The difference from cluster is the unit and the purpose. cluster forks whole processes to scale I/O across cores; worker_threads add threads within a process to offload CPU work off the main thread. Reach for cluster to serve more requests, worker_threads to keep one request's heavy computation from freezing the others.

clusterworker_threads
UnitSeparate processesThreads in one process
MemoryIsolated per processCan share via SharedArrayBuffer
Best forScaling I/O across CPU coresOffloading CPU-bound work
Startup costHigher (full process)Lower (thread)

Key point: The one-line answer the question needs: cluster scales I/O across cores, worker_threads offload CPU work. Lead with that, then add detail.

Q34. How do you run external commands or scripts with child_process?

The child_process module spawns other programs. spawn streams output and suits long-running or large-output commands; exec buffers the whole output and suits short commands (but risks buffer overflow on big output); fork is a special case that spawns a Node script with a message channel to the parent.

The security note that matters: exec runs a shell, so interpolating user input into the command string is a command-injection hole. Prefer spawn with an argument array, which doesn't invoke a shell.

javascript
const { spawn } = require("child_process");

// safe: args as an array, no shell interpolation
const ls = spawn("ls", ["-la", userDir]);
ls.stdout.on("data", (chunk) => process.stdout.write(chunk));
ls.on("close", (code) => console.log("exited", code));

Key point: The exec-runs-a-shell command-injection point turns a routine answer into a security-aware one. Always contrast spawn (array args, no shell) with exec.

Q35. What is the difference between session-based and JWT authentication?

Session auth stores state on the server: on login you create a session, store it (memory, Redis, a database), and hand the client a cookie holding the session id. Every request looks up that session. JWT auth is stateless: the server signs a token containing the user's claims, the client sends it on each request, and the server verifies the signature without a lookup.

The trade-off is state versus revocation. Sessions are trivially revocable (delete the record) but need shared storage to scale. JWTs scale without shared state but can't be revoked before expiry unless you add a denylist, which reintroduces the state you were avoiding. Keep JWT lifetimes short and pair with refresh tokens.

SessionsJWT
StateServer-side storeStateless (in the token)
RevocationImmediate (delete session)Hard until expiry (needs denylist)
ScalingNeeds shared store (Redis)No shared store needed
Best forTraditional web appsAPIs, microservices, mobile

Key point: The revocation trade-off is the follow-up interviewers chase. Say JWTs are hard to revoke and how you'd handle it (short expiry, refresh tokens, denylist).

Watch a deeper explanation

Video: JWT Authentication Tutorial - Node.js (Web Dev Simplified, YouTube)

Q36. What are the essential security practices for a Node.js API?

Validate and sanitize every input (a schema validator like Zod or Joi), use parameterized queries or an ORM to prevent SQL injection, hash passwords with bcrypt or argon2 (never plain or fast hashes), and set security headers with helmet. Add rate limiting, keep secrets in environment variables, and enable CORS deliberately rather than wide open.

The one that catches teams is dependencies: run npm audit, keep packages current, and remember a vulnerability three levels deep in your tree is still your vulnerability. Also avoid leaking stack traces to clients in production.

Key point: The a specific tool per risk (helmet, bcrypt, a validator, npm audit). Concrete tooling indicates real experience, not a checklist recital.

Q37. What is CORS and how do you handle it in Node.js?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism: by default a page can't call an API on a different origin unless that API opts in with the right response headers. It's enforced by the browser, not the server, which is why a request works from curl but fails in the browser.

In Express you configure it with the cors middleware: set the allowed origins explicitly, the allowed methods and headers, and whether credentials are allowed. The mistake is reflexively allowing all origins (*) with credentials, which the spec forbids and which weakens your security.

javascript
const cors = require("cors");

app.use(cors({
  origin: ["https://app.example.com"],   // explicit, not "*"
  methods: ["GET", "POST"],
  credentials: true,
}));

Q38. What makes a well-designed REST API?

Model resources as nouns and use HTTP methods as the verbs: GET /users, POST /users, GET /users/:id, PUT/PATCH /users/:id, DELETE /users/:id. Return the right status codes, keep responses consistent, and use plural nouns and nesting for relationships (GET /users/:id/orders).

Beyond the basics: version the API (/v1), paginate list endpoints, make writes idempotent where you can (PUT, DELETE), and return errors in a consistent shape. GET must be side-effect free, which is what lets clients and proxies cache it safely.

Key point: The idempotency and 'GET has no side effects' points separate The production-ready answer from a CRUD recital. Work at least one in.

Q39. Why does middleware order matter in Express?

Middleware runs in the order you register it, top to bottom, so order is logic. Body parsing must come before routes that read req.body. Authentication must come before the routes it protects. A 404 handler goes after all routes, and error-handling middleware goes last of all.

Get the order wrong and you get silent bugs: routes that see an empty body, auth that runs too late to protect anything, or an error handler that never fires because it was registered before the code that throws.

Q40. How do you connect to a database and manage connections in Node.js?

Use a connection pool, not a new connection per request. A pool keeps a set of open connections and hands them out, which avoids the cost of connecting on every query and caps how many connections you open against the database. Libraries like pg, mysql2, and ORMs (Prisma, Sequelize) create pools for you.

Two production details: always release connections back to the pool (finally blocks or the library's managed query), and set sensible pool limits so a traffic spike doesn't exhaust the database's connection cap.

javascript
const { Pool } = require("pg");
const pool = new Pool({ max: 10 });

async function getUser(id) {
  const { rows } = await pool.query(
    "SELECT * FROM users WHERE id = $1", [id]   // parameterized
  );
  return rows[0];
}

Q41. How do you add timeouts and cancellation to outbound requests?

Every outbound call needs a timeout, otherwise one slow dependency can pile up requests until the process runs out of resources. The modern tool is AbortController: create one, pass its signal to fetch (or a supporting library), and abort it on a timer. The request rejects with an abort error you catch and translate to a 503 or a retry.

The senior point is that timeouts belong on every network boundary, database, cache, and third-party API, tuned to how long the caller can actually wait, not left at the library default of 'forever'. Pair them with retries and a circuit breaker so a slow dependency degrades gracefully instead of taking the service down.

javascript
async function fetchWithTimeout(url, ms = 2000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);
  try {
    return await fetch(url, { signal: controller.signal });
  } finally {
    clearTimeout(timer);   // always clear the timer
  }
}

Key point: Saying 'every network call gets a timeout' is a strong production signal. Interviewers have seen outages caused by exactly the missing timeout you're naming.

Q42. How do you run async operations in parallel versus in sequence?

Awaiting in a loop runs operations one after another, which is right when each depends on the last. For independent operations, kick them all off and await together with Promise.all, which runs them concurrently and resolves when all finish, cutting total time to the slowest one.

Know the variants: Promise.all rejects on the first failure; Promise.allSettled waits for all and reports each outcome; Promise.race resolves on the first to settle. Choosing the right one is a common intermediate check.

javascript
// sequential: ~3x the time
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

// parallel: ~1x the slowest
const [a2, b2, c2] = await Promise.all([fetchA(), fetchB(), fetchC()]);

Q43. How do you test a Node.js application?

Unit test pure logic with Jest, Vitest, or the built-in node:test runner. Integration test API routes by sending real requests through the app with supertest and asserting status and body. Mock external boundaries (network, clock, third-party APIs) so tests are fast and deterministic.

A strong answer separates the levels: many fast unit tests, fewer integration tests over the real app and a test database, and a thin layer of end-to-end tests. Testing behavior at the route boundary catches more real bugs than over-mocking internals.

javascript
const request = require("supertest");
const app = require("../app");

test("GET /health returns 200", async () => {
  const res = await request(app).get("/health");
  expect(res.status).toBe(200);
  expect(res.body.ok).toBe(true);
});

Q44. How do you debug and inspect a running Node.js process?

the inspector: node --inspect (or --inspect-brk to break on the first line) opens a debugging port you attach Chrome DevTools or VS Code to, for breakpoints, stepping, and watching variables comes first. That beats scattering console.log for anything non-trivial.

For a live process you can't restart, send SIGUSR1 to enable the inspector, or use a sampling profiler like 0x or clinic to see where time goes. Structured logging (pino, winston) with request ids makes production issues traceable after the fact.

Q45. What is graceful shutdown and how do you implement it?

Graceful shutdown means stopping cleanly when the platform sends SIGTERM: stop accepting new connections, finish in-flight requests, close database pools and other resources, then exit. Without it, you drop live requests and leak connections on every deploy.

You listen for SIGTERM, call server.close() to stop accepting new requests while letting current ones finish, close your resources, and exit. Add a timeout so a stuck request can't block shutdown forever.

javascript
process.on("SIGTERM", async () => {
  server.close(async () => {         // stop new, finish in-flight
    await pool.end();                // close DB pool
    process.exit(0);
  });
  setTimeout(() => process.exit(1), 10000).unref(); // hard cap
});
Back to question list

Node.js Interview Questions for Experienced Developers

Experienced17 questions

advanced rounds probe internals, design judgment, and production scars. Expect every answer here to draw a follow-up.

Q46. What actually happens when V8 runs your JavaScript?

V8 parses your source to an abstract syntax tree, generates bytecode with the Ignition interpreter, and starts running immediately. As functions run hot, the TurboFan optimizing compiler recompiles them to fast machine code based on the types it has seen, and deoptimizes back to bytecode if those assumptions break.

The practical consequence: keeping object shapes and argument types stable helps V8 keep optimized code, while polymorphic functions that see many shapes stay slow. It's rarely worth micro-optimizing for, but understanding it explains why some hot paths surprise you.

Q47. How do you diagnose and fix a memory leak in Node.js?

Confirm the leak first: watch RSS and heap used climb across a steady workload without settling. Then take heap snapshots over time with --inspect and Chrome DevTools (or the heapdump module) and diff them to find which object type keeps growing and what's retaining it.

The usual culprits: unbounded caches and module-level arrays or maps that only grow, event listeners added but never removed, closures capturing large objects, and timers that keep references alive. Fixes are a bounded cache (an LRU with a max size), removing listeners, and breaking retained references, then re-measure to confirm.

Heap over time: leaking vs healthy service

Heap used across a steady workload. A leak climbs without settling; a healthy service sawtooths as GC reclaims memory.

Leaking (hour 4)
1,400 MB
Healthy (hour 4)
320 MB
  • Leaking (hour 4): Heap keeps climbing; RSS never settles between GCs
  • Healthy (hour 4): Heap sawtooths around a stable baseline as GC reclaims

Key point: Lead with 'measure before fixing' and The snapshot-diff workflow. Jumping straight to a suspected cause without measuring is the answer that loses points.

Q48. What is event loop lag, and how do you detect and prevent it?

Event loop lag is the delay between when a callback should run and when it actually does, caused by something hogging the single thread: a synchronous CPU-heavy operation, a giant JSON.parse, a tight loop, or a blocking library call. While the thread is busy, every other request waits.

Detect it by measuring the loop's delay (perf_hooks monitorEventLoopDelay, or a simple timer that records how late it fires) and alerting on the p99. Prevent it by moving CPU work to worker_threads, chunking large synchronous operations, streaming instead of buffering, and never using the sync fs or crypto APIs on the request path.

javascript
const { monitorEventLoopDelay } = require("perf_hooks");
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
  console.log("loop p99 (ms):", (h.percentile(99) / 1e6).toFixed(1));
}, 5000);

Key point: The a concrete cause (a big synchronous JSON.parse) and a concrete fix (worker_threads). Abstract answers about 'blocking' lose to specific ones here.

Q49. How do you build a custom Transform stream, and why would you?

Subclass Transform (or pass a transform function) and implement _transform(chunk, encoding, callback): process the chunk, push results, and call the callback when done. Optionally implement _flush for trailing data. You build one to process data incrementally as it flows: parse NDJSON line by line, redact fields in a log pipeline, or reshape records between a source and a sink without buffering the whole dataset.

The senior detail is respecting backpressure and object mode: honor the highWaterMark, set objectMode when you're passing objects rather than bytes, and propagate errors through the callback so pipeline can clean up.

javascript
const { Transform } = require("stream");

const upper = new Transform({
  transform(chunk, _enc, cb) {
    cb(null, chunk.toString().toUpperCase());
  },
});

source.pipe(upper).pipe(destination);

Q50. How would you design communication between Node.js microservices?

Split by call pattern. Synchronous request/response (a service needs an answer now) goes over HTTP or gRPC, with timeouts, retries with backoff, and a circuit breaker so a slow dependency doesn't cascade. Asynchronous, event-driven work (a service reacts to something that happened) goes over a message broker (Kafka, RabbitMQ, SQS), which decouples services and absorbs bursts.

The judgment interviewers probe: don't make everything synchronous (you build a distributed monolith that fails together), and don't make everything async (you lose the simplicity where a direct call is fine). Also plan for idempotency, since at-least-once delivery means consumers must handle duplicates.

Key point: Naming circuit breakers, retries with backoff, and idempotent consumers signals you've run services in production, not just read about the pattern.

Q51. What caching strategies do you use in a Node.js service?

Layer them. In-process caching (a Map or an LRU) is fastest but per-instance and lost on restart, good for small hot data. A shared cache like Redis is the workhorse: it survives restarts, is shared across instances, and supports TTLs and eviction. HTTP caching (Cache-Control, ETags) pushes work to clients and CDNs for cacheable GETs.

The hard parts are invalidation and stampedes. Set TTLs so stale data self-heals, invalidate on writes for correctness-critical data, and guard against a cache stampede (many requests recomputing the same expired key at once) with a lock or a single-flight pattern.

Q52. How do you scale a Node.js application horizontally?

Keep instances stateless: session and cache state live in Redis or a database, not in process memory, so any instance can serve any request. Then run many instances behind a load balancer and scale the count with load. Inside one instance, cluster or one-process-per-container uses all CPU cores.

The constraints that decide the ceiling are usually downstream: the database connection cap (which is why pooling and connection limits matter), and shared resources that don't scale as easily as the stateless tier. The senior framing is that scaling Node is mostly about pushing state out and finding the real bottleneck, which is rarely the Node process itself.

Q53. How do you implement health checks and readiness probes?

Expose two endpoints. Liveness answers 'is the process alive and not deadlocked?' and stays cheap so the orchestrator doesn't kill a healthy process under load. Readiness answers 'can this instance serve traffic right now?' and checks real dependencies (database reachable, migrations done, caches warm), returning non-200 when the instance shouldn't receive requests.

The distinction matters operationally: a failed liveness check restarts the pod, a failed readiness check just pulls it out of the load balancer until it recovers. Conflating them causes restart loops during transient dependency blips.

javascript
app.get("/livez", (req, res) => res.sendStatus(200)); // cheap

app.get("/readyz", async (req, res) => {
  try {
    await pool.query("SELECT 1");   // real dependency check
    res.sendStatus(200);
  } catch {
    res.sendStatus(503);            // pull from LB, don't restart
  }
});

Q54. How do you make a Node.js service observable in production?

Three pillars. Structured logs (pino or winston as JSON) with a correlation id per request so you can trace one request across services. Metrics (request rate, error rate, latency percentiles, event loop lag, memory) exported to Prometheus or a hosted APM. Distributed tracing (OpenTelemetry) that follows a request across service boundaries to find where latency actually accrues.

The point the key signal is: log at the boundaries with context, alert on symptoms users feel (error rate, p99 latency) rather than raw resource graphs, and make sure a request id ties logs, metrics, and traces together.

Q55. Why does connection pooling matter, and how do you size a pool?

Opening a database connection is expensive (a TCP handshake plus auth), so a per-request connection adds latency and can exhaust the database's connection limit under load. A pool amortizes that by reusing a fixed set of connections. Size it from the database's max connections divided across all your instances, leaving headroom, rather than picking a number blindly.

The counterintuitive part: bigger pools aren't better. Past the point where the database can run queries concurrently, extra connections just queue and add contention. A small pool with fast queries usually beats a large pool that overwhelms the database. Measure query latency under load to find the right size.

Q56. What is the N+1 query problem and how do you fix it in Node.js?

N+1 is one query to fetch a list, then one more query per item to fetch its related data: fetch 100 users, then 100 separate queries for each user's orders. It's easy to write with an ORM inside a loop or a naive GraphQL resolver, and it turns one page load into 101 round trips.

Fix it by batching: a single query with a join or a WHERE id IN (...) that fetches all the related rows at once, then stitch them together in memory. In GraphQL, DataLoader batches and caches per-request to collapse the N calls into one. The tell in an interview is spotting a query inside a loop.

javascript
// N+1: a query per user
for (const u of users) {
  u.orders = await db.query("SELECT * FROM orders WHERE user_id=$1", [u.id]);
}

// batched: one query for all
const ids = users.map((u) => u.id);
const orders = await db.query(
  "SELECT * FROM orders WHERE user_id = ANY($1)", [ids]
);

Key point: The interview tell is 'a query inside a loop'. If you're given code to review, that pattern is what they planted for you to catch.

Q57. What are the real-world friction points between ESM and CommonJS?

The asymmetry causes most pain. ESM can import CommonJS (the default export becomes module.exports), but CommonJS can't require() a package that ships only ESM, which is why 'ERR_REQUIRE_ESM' appears when a dependency goes ESM-only. In ESM there's no require, __dirname, or __filename, so you use createRequire or import.meta.url. And ESM named imports from CommonJS are inferred and sometimes miss, forcing default-import-then-destructure.

The pragmatic answers: for a new project, pick ESM. For a large CommonJS codebase, migrate incrementally or use a bundler. Node's dual-package and conditional exports let a library ship both, which is why authoring one correctly is fiddly.

Q58. How do async iterators relate to streams, and when do you prefer them?

A readable stream is async-iterable, so you can consume it with for await...of, which reads chunks with automatic backpressure and far less ceremony than wiring 'data', 'end', and 'error' listeners by hand. Errors surface as a normal throw you catch with try/catch.

Prefer async iteration when you're consuming a stream with per-chunk logic and want readable, linear code. Stick with pipe/pipeline when you're composing a pipeline of transforms end to end, where pipeline's error handling and cleanup are the point. They compose: you can iterate the output of a pipeline.

javascript
const fs = require("fs");
const readline = require("readline");

const rl = readline.createInterface({
  input: fs.createReadStream("huge.log"),
});
for await (const line of rl) {   // backpressure handled
  process(line);
}

Q59. Design question: how would you implement a middleware chain from scratch?

Model it as an array of functions, each with the signature (req, res, next). Keep an index, and let next() advance to the function at the next index and invoke it, passing a next that advances again. When a middleware responds instead of calling next(), the chain stops. Wrapping each call in a try/catch (or Promise handling) lets a thrown error skip ahead to error middleware.

The design points the question needs surfaced: middleware runs in order, next() is what passes control (and calling it twice is a bug), and error handling is a separate lane keyed by the four-argument signature. Getting the recursion and the stop condition right is the core of the exercise.

javascript
function runChain(middlewares, req, res) {
  let i = 0;
  function next(err) {
    const fn = middlewares[i++];
    if (!fn) return;
    try {
      err ? fn(err, req, res, next) : fn(req, res, next);
    } catch (e) {
      next(e);   // jump toward error middleware
    }
  }
  next();
}

Key point: This tests whether you truly understand next(). Narrate the index advance and the stop condition; that's what matters, not perfect syntax.

Q60. Design question: how would you build a distributed rate limiter in Node.js?

Clarify the requirements first (per-user or global, burst tolerance, and single-instance versus distributed), then pick the algorithm. A token bucket gives smooth rates with burst capacity. Single instance: keep buckets in an in-process Map keyed by user. Distributed across many Node instances: the state must move to Redis, because per-process memory can't see global traffic. Use an atomic Redis operation (a Lua script, or INCR with EXPIRE) so concurrent requests can't race past the limit.

Close on the operational edges: what to return on rejection (429 with a Retry-After header), how to handle Redis being down (fail open or closed, a real decision), and emitting metrics on rejects. Structuring the answer clarify, algorithm, storage, edges is what the question is really scoring.

javascript
// fixed window with an atomic Redis op
async function allow(userId, limit, windowSec) {
  const key = `rl:${userId}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, windowSec);
  return count <= limit;   // 429 + Retry-After when false
}

Q61. How do you add TypeScript to a Node.js project, and what does it buy you?

Add the typescript compiler and @types packages for Node and your libraries, configure tsconfig.json (target, module, strict), and either build to JavaScript with tsc for production or run directly in development with tsx or ts-node. Recent Node versions can also strip types and run .ts files without a separate compile step.

What it buys you at scale is catching whole classes of errors before runtime (undefined access, wrong shapes, bad refactors), self-documenting function signatures, and safer refactoring across a large codebase. The cost is build tooling and some friction with loosely typed libraries, which is why teams turn strict on gradually.

Q62. A Node.js service is slow or crashing in production. Walk through your approach.

Observe first: check error rate, latency percentiles, memory, and event loop lag on your dashboards, and correlate the onset with recent deploys or traffic changes. Read the structured logs and traces around the slow or failing path before touching anything. Is it CPU (event loop blocked), memory (heap climbing toward a crash), a downstream dependency (database slow, timeouts missing), or a specific route?

Then form a hypothesis and verify it with the right tool: py-spy-style live profiling with 0x or clinic for CPU, a heap snapshot diff for memory, loop-delay metrics for blocking. Fix at the layer the evidence points to, ship it, and confirm with the same metrics. The structure, observe, hypothesize, verify, fix, confirm, matters more than any single tool.

Key point: The methodology is; tools support the evidence.

Back to question list

Why Node.js? Node vs Python, Go, and Deno for backends

Node.js wins when a service is I/O-bound and you want one language across the stack: JavaScript on the client and the server, a huge npm ecosystem, and an event loop that holds many idle connections cheaply. It trades raw CPU throughput and type safety for that reach and speed of development. Where it loses is heavy computation (the single thread blocks) and teams that want batteries-included concurrency or static types out of the box, where Go or Python fits better. Knowing these trade-offs out loud is itself an interview signal: it shows you pick a runtime on merits, not habit.

RuntimeModelBest atWatch out for
Node.jsSingle-threaded, event loop + libuv thread poolI/O-bound APIs, real-time, streaming, full-stack JSCPU-bound work blocks the loop; no built-in types
PythonSingle-threaded per process, GILData, ML, scripting, rapid prototypingCPU parallelism (GIL); async ecosystem is newer
GoGoroutines on an M:N schedulerConcurrent services, low latency, CPU workSmaller data/ML ecosystem; more verbose
DenoSingle-threaded event loop, secure by defaultModern TypeScript-first apps, tooling in one binarySmaller ecosystem; some npm packages need shims

How to Prepare for a Node.js Interview

Prepare in layers, and practice out loud. Most Node.js rounds move from runtime-model questions to live coding to a backend design or debugging discussion, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet in a real Node process; watching the event loop misbehave teaches more than reading about it.
  • Practice thinking aloud on small API problems with a timer, because your process, not just the final answer is the technical point.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical Node.js interview flow

1Recruiter or phone screen
background, motivation, a few runtime-model checks
2Runtime and language concepts
event loop, blocking vs non-blocking, streams, error handling
3Live coding
build or fix a small Express route or async function under observation
4Design or debugging
API design, production behavior, reading unfamiliar code, follow-ups

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: Node.js Quiz

Ready to test your Node.js knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Node.js topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a Node.js interview?

They cover the question-answer portion well, but most Node.js rounds also include live coding: building or fixing an async function or an Express route while explaining your thinking. solving small problems out loud with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Do I need to know Express, or is core Node.js enough?

Know core Node.js first: the event loop, streams, buffers, and error handling are what separate strong candidates. Express matters because most Node.js jobs use it or something like it, so understand middleware, routing, and error-handling middleware. If a job posting names a different framework (Fastify, NestJS), the middleware and request-lifecycle ideas transfer directly.

How long does it take to prepare for a Node.js interview?

If you use Node.js at work or in study, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write code daily; reading answers without running them is how preparation quietly fails, especially for async behavior you have to see to believe.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, the event loop, non-blocking I/O, streams, error handling, and the phrasing takes care of itself.

Is there a way to test my Node.js knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It's the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages, with full backend setups for Node.js. These questions reflect what actually gets asked and evaluated inside the interviews we host for 5,000+ HR teams.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 2 Jun 2026Last updated: 20 Jun 2026
Share: