All interview questions Web · 2026

Node.js Interview Questions

Node.js is one of the most common backend topics in web and full-stack interviews, and the questions are remarkably consistent. These are the ones interviewers actually ask, grouped by theme and tagged by experience level.

122 questions with concise, interview-ready answers.

Node.js Fundamentals

1.

What is Node.js?

Fresher

Node.js is a runtime that lets you run JavaScript outside the browser, built on Chrome's V8 engine. It uses an event-driven, non-blocking I/O model, which makes it lightweight and efficient for I/O-heavy and real-time applications. It ships with a large standard library and the npm ecosystem for building servers, tools, and APIs.

2.

Is Node.js single-threaded?

Fresher

The JavaScript execution and the event loop run on a single main thread, so your code runs one operation at a time. However, Node is not entirely single-threaded — libuv maintains a background thread pool that handles file I/O, DNS, and some CPU-bound work, and the OS handles network I/O asynchronously. This lets a single thread manage thousands of concurrent connections.

3.

What is the difference between blocking and non-blocking code?

Fresher

Blocking code stops the execution of further JavaScript until the current operation completes, occupying the single main thread (for example, fs.readFileSync). Non-blocking code starts the operation and continues executing, handling the result later via a callback or promise (for example, fs.readFile). Because Node runs on one thread, blocking operations hurt throughput, so non-blocking APIs are preferred for I/O.

4.

What is V8 and what does it do for Node.js?

Fresher

V8 is Google's open-source JavaScript engine, the same one that powers Chrome. It parses JavaScript, compiles it to native machine code with a just-in-time compiler, and manages the heap and garbage collection. Node embeds V8 for execution and adds libuv plus a set of C++ bindings for the things the language itself has no concept of, such as files, sockets, and processes.

5.

How is Node.js different from JavaScript in the browser?

Fresher

The language is the same, but the host objects differ. The browser gives you window, document, the DOM, localStorage and fetch, and enforces the same-origin policy; Node gives you global, process, require, Buffer, and direct access to the file system, network sockets and child processes. Code that touches the DOM cannot run in Node, and code that reads files cannot run in a browser.

6.

What kinds of applications is Node.js good and bad at?

Fresher

Node is excellent for I/O-bound workloads — REST and GraphQL APIs, real-time services with WebSockets, streaming, proxies and BFF layers — because one thread can wait on thousands of sockets cheaply. It is a poor fit for sustained CPU-bound work such as video encoding, large image processing or heavy cryptographic hashing, because that work blocks the single event loop thread. The honest answer in an interview is that CPU-heavy work belongs in worker threads, a separate service, or a different runtime.

7.

What is the global object in Node.js?

Fresher

It is called global, and it is the top-level scope object equivalent to window in a browser. Node also exposes globalThis, which works in both environments and is the portable choice. Note that in a CommonJS module the top-level scope is the module, not global, so declaring a variable at the top of a file does not attach it to global the way a top-level script does in a browser.

8.

What is the process object used for?

Fresher

process is a global that represents the running Node process. You use process.env for configuration, process.argv for command line arguments, process.exit() to terminate with a status code, process.cwd() for the working directory, and process.on() to listen for signals such as SIGTERM or events such as uncaughtException. It is also where you read memory usage with process.memoryUsage() during debugging.

9.

How do you read command line arguments in Node.js?

Fresher

process.argv is an array where index 0 is the Node executable, index 1 is the script path, and everything from index 2 onwards is the user-supplied arguments. For anything beyond a positional argument or two, use a parser — the built-in util.parseArgs, or a library like commander or yargs — rather than hand-rolling flag parsing.

10.

What is the REPL in Node.js?

Fresher

REPL stands for Read-Eval-Print Loop — the interactive shell you get by typing node with no arguments. It reads an expression, evaluates it, prints the result and loops, which makes it useful for trying an API or inspecting a value quickly. It supports multi-line input, tab completion, and an underscore variable holding the last result.

11.

What is the difference between concurrency and parallelism in Node.js?

2–5 yrs

Concurrency is handling many operations that are in progress at the same time; parallelism is executing multiple operations at literally the same instant on different cores. Node gives you high concurrency on one thread by interleaving I/O waits, but not parallelism for JavaScript. For real parallelism you need worker threads, the cluster module, or multiple processes.

12.

What are the main variants of the fs module API?

2–5 yrs

The fs module offers three flavours: callback-based (fs.readFile), synchronous (fs.readFileSync), and promise-based (fs.promises.readFile, or the node:fs/promises import). Use the promise API with async/await in application code, the callback API when integrating with older callback code, and the sync API only at startup or in scripts where blocking is harmless. Calling a sync method inside a request handler is the classic mistake — it stalls every other connection.

The Event Loop & Async Model

13.

What does non-blocking I/O mean in Node.js?

Fresher

Non-blocking I/O means an I/O operation (reading a file, querying a database, making a network call) is started and then control returns immediately to the program instead of waiting for the result. When the operation finishes, a callback or promise is scheduled to run. This lets the single main thread keep handling other requests instead of sitting idle, which is the core of Node's scalability.

14.

What is the event loop in Node.js?

Fresher

The event loop is the mechanism that lets Node perform non-blocking I/O on a single thread by offloading operations and processing their callbacks when they complete. It continuously checks queues of pending callbacks and executes them in phases. It is provided by libuv and is what allows Node to handle concurrency without spawning a thread per request.

15.

What are the phases of the event loop?

2–5 yrs

Each iteration (tick) of the loop runs through ordered phases: timers (setTimeout/setInterval callbacks), pending callbacks (some deferred system callbacks), idle/prepare (internal), poll (retrieves new I/O events and runs their callbacks), check (setImmediate callbacks), and close callbacks (e.g. socket close events). Between phases, the microtask queue — process.nextTick and resolved promises — is drained.

16.

What is libuv?

2–5 yrs

libuv is the C library that gives Node its asynchronous, event-driven capabilities. It implements the event loop, the thread pool, and a consistent abstraction over OS-level async I/O (epoll on Linux, kqueue on macOS, IOCP on Windows). When you do async file or network operations in Node, libuv is doing the underlying work.

17.

What is the difference between process.nextTick and setImmediate?

2–5 yrs

process.nextTick() schedules a callback to run immediately after the current operation completes, before the event loop continues to the next phase — it runs in the microtask queue. setImmediate() schedules a callback to run in the check phase of the next event loop iteration. So process.nextTick fires sooner; overusing it can starve the I/O phases and block the loop.

18.

What is the difference between the microtask queue and the macrotask queue?

2–5 yrs

Macrotasks are the callbacks scheduled into event loop phases — timers, I/O callbacks, setImmediate. Microtasks are promise reactions and queueMicrotask callbacks, plus Node's separate process.nextTick queue. After each macrotask, and between phases, Node drains the entire microtask queue before moving on, and the nextTick queue is drained before the promise queue. That is why a promise chain always resolves before the next setTimeout fires.

19.

What is the difference between setTimeout with zero delay and setImmediate?

2–5 yrs

setTimeout(fn, 0) schedules the callback into the timers phase; setImmediate(fn) schedules it into the check phase, which comes after poll. Inside an I/O callback the order is deterministic — setImmediate always runs first, because the loop is already past timers and heads into check next. Interviewers ask this to see whether you can name the phase rather than guess.

20.

Why is the order of setTimeout and setImmediate non-deterministic in the main module?

Senior

When both are scheduled from the top-level module, the result depends on how long process startup took. A timer with a zero delay is clamped to one millisecond, so if the loop reaches the timers phase after that millisecond has already elapsed the timer fires first; if startup was fast enough that the millisecond has not passed, the loop falls through to check and setImmediate wins. The lesson is never to rely on cross-phase ordering for correctness.

21.

What is the libuv thread pool and what uses it?

2–5 yrs

libuv keeps a small pool of background threads — four by default — for operations the OS has no good async primitive for. That includes file system operations, DNS lookups via dns.lookup, and the crypto functions pbkdf2, scrypt, randomBytes and zlib compression. Network sockets do not use the pool, because epoll, kqueue and IOCP already provide non-blocking notification.

22.

How do you change the size of the libuv thread pool, and when should you?

Senior

Set the UV_THREADPOOL_SIZE environment variable before the process starts; it must be set before the first use because the pool is created lazily and then fixed. Raising it helps when you are saturating the pool with file or crypto work and see latency that disappears under lower load. It does not help network-bound work, and setting it far above the core count just adds context switching.

23.

What happens if you run a long CPU-heavy loop on the main thread?

Fresher

Nothing else runs. The event loop cannot advance to the next phase until your synchronous code returns, so timers do not fire, queued I/O callbacks do not run, and every pending HTTP request hangs until the loop is free. This is why a single expensive JSON.parse, a synchronous hash, or an unbounded loop can make an entire Node server look like it has crashed.

24.

How do you stop a CPU-bound task from blocking the event loop?

Senior

Move it off the main thread: a worker thread for in-process parallelism with shared memory, a child process or a separate service for heavier isolation, or a job queue consumed by dedicated workers. If the work must stay in-process and is chunkable, you can yield periodically with setImmediate so the loop drains other callbacks between slices. Adding more async syntax does not help — await does not make synchronous work concurrent.

25.

How do you measure event loop lag?

Senior

Schedule a timer for a known interval and measure how much later than expected it actually fires — the difference is the lag. Node exposes this properly through perf_hooks.monitorEventLoopDelay(), which gives you a histogram with percentiles rather than a single sample. Rising p99 loop delay under load is the clearest single signal that something synchronous is running inside your request path.

26.

Does async/await make code run in parallel?

2–5 yrs

No. await suspends the current async function until the promise settles, so awaiting three calls one after another runs them sequentially and takes the sum of their durations. To overlap them, start all the promises first and then await Promise.all on the array. Writing sequential awaits where parallel calls were possible is one of the most common performance bugs interviewers probe for.

Asynchronous Patterns & Error Handling

27.

What is the difference between callbacks, promises, and async/await?

Fresher

Callbacks are functions passed to be invoked when an async operation finishes, but nesting them leads to hard-to-read code. Promises represent a future value and can be chained with .then()/.catch(), flattening that nesting. Async/await is syntactic sugar over promises that lets you write asynchronous code in a synchronous-looking style using try/catch for errors, and it is the modern preferred approach.

28.

What is callback hell and how do you avoid it?

Fresher

Callback hell is deeply nested callbacks — the "pyramid of doom" — that occurs when multiple asynchronous operations depend on each other, making code hard to read and maintain. You avoid it by using promises with chaining, async/await for flat sequential code, or named functions and modularization instead of inline anonymous callbacks.

29.

How do you handle errors in Node.js?

2–5 yrs

For synchronous code and async/await, use try/catch blocks. For promises, use .catch(); for callbacks, follow the error-first convention where the first argument is the error. In Express, error-handling middleware with four arguments (err, req, res, next) centralizes errors, and you should also listen for uncaughtException and unhandledRejection as a last resort.

30.

What is the error-first callback convention?

Fresher

By convention, an asynchronous callback in Node takes the error as its first argument and the result as the second, so you check if (err) before touching the data. It exists because a thrown exception inside an async callback cannot be caught by the caller's try/catch — the stack that called it is long gone. Forgetting to check the error argument is how bugs silently pass undefined data down the chain.

31.

What is util.promisify?

2–5 yrs

util.promisify takes a function following the error-first callback convention and returns a version that returns a promise instead, so you can await it. It is how you modernise older APIs and libraries that predate promises. Many core modules now ship promise variants directly — node:fs/promises, node:dns/promises, node:timers/promises — so reach for those first.

32.

What is the difference between Promise.all, Promise.allSettled, Promise.race and Promise.any?

2–5 yrs

Promise.all resolves with an array of all results but rejects as soon as any input rejects. Promise.allSettled always resolves, giving you a status and value or reason for each. Promise.race settles with the first promise to settle, whether it fulfils or rejects. Promise.any resolves with the first fulfilment and only rejects if every input rejects, with an AggregateError.

33.

How do you run several asynchronous operations in parallel with async/await?

2–5 yrs

Create the promises first, without awaiting, then await them together: const [a, b] = await Promise.all([getA(), getB()]). The calls start as soon as the functions are invoked, so the total time is the slowest one rather than the sum. If a partial failure is acceptable, use Promise.allSettled so one rejection does not discard the successful results.

34.

What happens to an error thrown inside an async function?

2–5 yrs

It does not propagate synchronously — the async function returns a rejected promise instead. That means the caller must await it inside try/catch or attach a .catch(); if nobody does, it becomes an unhandled rejection. This is why calling an async function without awaiting it, sometimes called a floating promise, hides failures so effectively.

35.

What is an unhandled promise rejection and what does Node do with it?

2–5 yrs

It is a rejected promise with no rejection handler attached by the time the microtask queue drains. Since Node 15 the default behaviour is to print the error and terminate the process with a non-zero exit code, matching how an uncaught exception behaves. You can observe it with process.on('unhandledRejection'), but that handler should log and shut down cleanly, not swallow the error and continue.

36.

Why does try/catch not catch an error from a callback-based API?

2–5 yrs

Because the callback runs later, on a different turn of the event loop, when the try block has already exited and its stack has unwound. The error therefore escapes to the top level as an uncaught exception. Callback APIs solve this by passing the error into the callback instead of throwing, which is exactly why the error-first convention exists.

37.

What is the difference between an operational error and a programmer error?

Senior

Operational errors are expected runtime problems in a correct program — a socket timeout, a 404 from an upstream API, invalid user input, a full disk — and should be handled and reported. Programmer errors are bugs, such as reading a property of undefined or passing the wrong type, and cannot be meaningfully recovered from. The practical rule is: handle operational errors explicitly, and let programmer errors crash the process so a supervisor restarts it in a known-good state.

38.

Should you keep the process running after an uncaughtException?

Senior

No. By the time uncaughtException fires, the stack has unwound in the middle of unknown work, so state such as open transactions or half-written buffers may be inconsistent. The accepted pattern is to log the error, stop accepting new work, flush what you can, and exit so a process manager restarts a clean instance. Treating the handler as a way to ignore crashes leads to processes that leak resources and serve corrupt data.

39.

What is AbortController and how is it used in Node.js?

Senior

AbortController gives you a signal you can pass to an async API and later abort, which rejects the operation with an AbortError. Node supports it in fetch, fs promises, streams, timers promises and many libraries, so you can cancel an outbound request when the client disconnects or a deadline passes. It is the standard replacement for ad-hoc cancellation flags, and it composes — AbortSignal.timeout() and AbortSignal.any() build common cases for you.

40.

How do you add a timeout to a promise?

2–5 yrs

Race the work against a timer: Promise.race([work(), rejectAfter(ms)]). The important caveat is that racing does not stop the losing promise — the underlying request keeps running and holding resources. For real cancellation, pass an AbortSignal, ideally AbortSignal.timeout(ms), so the operation itself is torn down.

41.

What is AsyncLocalStorage used for?

Senior

AsyncLocalStorage, from the async_hooks module, keeps a value associated with an asynchronous call chain, so anything running under a given request can read it without threading a parameter through every function. The classic use is a request id or trace context for logging, and per-request auth or tenant information. It is the Node equivalent of thread-local storage, and it costs a little performance, so store identifiers rather than large objects.

Modules & npm

42.

What is the difference between CommonJS (require) and ES Modules (import)?

Fresher

CommonJS uses require() and module.exports, loads modules synchronously, and is the traditional Node module system. ES Modules use import/export, are loaded asynchronously, support top-level await and static analysis, and are the standard JavaScript module format. You opt into ESM with "type": "module" in package.json or the .mjs extension.

43.

What is package.json and what is npm?

Fresher

package.json is the manifest file that describes a Node project — its name, version, scripts, and dependencies. npm (Node Package Manager) is the default tool and registry for installing, publishing, and managing those dependencies. Running npm install reads package.json, fetches the packages, and records exact versions in package-lock.json for reproducible installs.

44.

How does require() resolve a module path?

2–5 yrs

If the argument starts with ./ or ../ it is resolved as a relative file, trying the exact path, then the .js, .json and .node extensions, then the directory's package.json main field, then index.js. If it starts with node: or names a core module, the built-in wins. Otherwise Node walks up the directory tree looking in each node_modules folder until it finds a match or reaches the root.

45.

What is the difference between module.exports and exports?

Fresher

exports starts as a reference to module.exports, so adding properties to it works. Reassigning exports to a new object breaks that reference and exports nothing, because require() returns module.exports. The safe habit is to assign to module.exports when replacing the whole export, and only use exports for attaching named properties.

46.

Are modules cached in Node.js?

2–5 yrs

Yes. The first require() of a resolved path evaluates the file and stores the result in require.cache; every later require of the same path returns that same object without re-running the module. This makes top-level module state effectively a singleton across your process, which is how many people implement a shared database pool or config object — and also why a stateful module can surprise you in tests.

47.

What happens with circular dependencies in CommonJS?

Senior

Node does not error; it returns whatever the partially executed module has exported so far. If A requires B and B requires A back, B receives A's incomplete exports object, so any property A had not yet assigned is undefined at that moment. The usual symptoms are a mysterious undefined or "is not a function" at startup, and the fixes are to move the require inside the function that needs it, or to extract the shared piece into a third module.

48.

How do circular dependencies behave differently in ES Modules?

Senior

ESM is resolved and linked before evaluation, so bindings are hoisted live references rather than a snapshot of an object. A cycle therefore works as long as the imported binding is not read before the other module has finished initialising it — reading it too early throws a ReferenceError from the temporal dead zone rather than silently giving undefined. Failing loudly is an improvement, but the design fix is still to break the cycle.

49.

What is the difference between dependencies, devDependencies and peerDependencies?

Fresher

dependencies are needed at runtime and are installed for anyone who installs your package. devDependencies are only needed to develop, build or test it — test runners, linters, type definitions, bundlers — and are skipped by npm install --omit=dev. peerDependencies declare a package your library expects the host application to provide, such as a framework, so you do not bundle a second copy of it.

50.

What does semantic versioning mean, and what do the caret and tilde do?

Fresher

Semantic versioning is MAJOR.MINOR.PATCH, where major means breaking changes, minor means backwards-compatible features and patch means backwards-compatible fixes. The caret, as in ^1.2.3, allows anything up to but not including 2.0.0; the tilde, as in ~1.2.3, allows patches only, up to but not including 1.3.0. An exact pin such as 1.2.3 allows nothing, which is safest but leaves security fixes to you.

51.

What is package-lock.json and should you commit it?

Fresher

It records the exact resolved version, integrity hash and tree layout of every direct and transitive dependency, so a later install reproduces the same node_modules. Yes, commit it for applications — without it two machines installing "^1.2.3" a month apart can get different code. For a published library the lockfile does not affect consumers, but committing it still stabilises your own CI.

52.

What is the difference between npm install and npm ci?

2–5 yrs

npm install reads package.json, may update the lockfile and mutates the existing node_modules. npm ci deletes node_modules, installs strictly from package-lock.json, and fails outright if the lockfile and package.json disagree. That makes npm ci the correct command in CI and deployment pipelines, where a reproducible and unchanged tree matters more than convenience.

53.

What is npx?

Fresher

npx runs a package binary without installing it globally — it uses the local node_modules copy if present, otherwise downloads it to a cache for that invocation. It is how you run one-off tools such as scaffolding generators, and how you make sure a script uses the project's pinned version of a CLI rather than whatever is installed system-wide.

54.

What do the main, module, exports and type fields in package.json do?

Senior

main is the legacy CommonJS entry point; module is a non-standard convention bundlers use to find an ESM build. type: "module" makes .js files in the package ES modules, and type: "commonjs" (the default) makes them CommonJS. exports is the modern replacement — it maps subpath entry points, supports separate import and require conditions for dual packages, and importantly blocks deep imports into files you did not explicitly expose.

55.

Why do __dirname and __filename not exist in ES Modules?

2–5 yrs

They are CommonJS module-wrapper variables, and ESM has no wrapper. In ESM you derive them from import.meta.url, either with import.meta.dirname on modern Node, or by passing import.meta.url through fileURLToPath from the node:url module. Interviewers use this to check whether you have actually migrated a project to ESM rather than only read about it.

Streams, Buffers & Events

56.

What are streams in Node.js?

Fresher

Streams are objects for reading or writing data piece by piece instead of loading it all into memory at once, which is efficient for large files or network data. There are four types: Readable, Writable, Duplex (both), and Transform (modifies data as it passes). You commonly connect them with pipe(), for example streaming a file to an HTTP response.

57.

What is the EventEmitter in Node.js?

Fresher

EventEmitter is a core class (from the events module) that implements the publish-subscribe pattern. Objects emit named events with emit(), and listeners registered with on() or once() respond to them. Many built-in Node objects — streams, HTTP servers, and processes — inherit from EventEmitter, making it central to Node's event-driven architecture.

58.

Why would you stream a large file instead of reading it into memory?

Fresher

fs.readFile loads the entire file into a Buffer, so a 2 GB upload becomes 2 GB of heap per concurrent request and will hit the V8 heap limit or exhaust the machine. Streaming reads it in chunks of a few kilobytes, so memory stays flat regardless of file size and the first byte reaches the client sooner. The trade-off is that you cannot random-access the content, and error handling is more involved.

59.

What is backpressure and how do streams handle it?

Senior

Backpressure is what happens when a producer generates data faster than the consumer can accept it. A writable stream's write() returns false once its internal buffer passes highWaterMark, which is the signal to stop writing and wait for the drain event before resuming. pipe() and pipeline() implement this handshake for you; manually ignoring the false return is how memory usage climbs until the process dies.

60.

What does pipe() do, and why is pipeline() preferred?

Senior

pipe() connects a readable stream to a writable one and handles backpressure, but it does not forward errors or clean up the other streams when one fails, which leaks file descriptors and sockets. stream.pipeline() chains any number of streams, propagates errors to a single callback or promise, and destroys every stream in the chain on failure. Use pipeline (or its promise form from node:stream/promises) in production code.

61.

What is the difference between flowing and paused mode in a readable stream?

Senior

In paused mode you pull data explicitly with read(); in flowing mode data is pushed to you as fast as it arrives via data events. Attaching a data listener, calling resume(), or piping switches the stream into flowing mode. The risk is that flowing mode without a consumer that respects backpressure will buffer or drop data, so prefer pipe/pipeline or async iteration with for await.

62.

How do you handle errors in a stream?

2–5 yrs

Streams are EventEmitters, so an unhandled error event throws and crashes the process — every stream needs either an error listener or a wrapper that supplies one. The practical approach is to use stream.pipeline, which surfaces any error from any stage in one place and destroys the rest of the chain. Remember that an error on a readable does not automatically close the writable, which is exactly the leak pipeline fixes.

63.

How do you create a custom Transform stream?

2–5 yrs

Extend stream.Transform and implement _transform(chunk, encoding, callback), calling this.push() for output and then callback(), plus an optional _flush for trailing data. You can also pass the transform function directly to the Transform constructor for short cases. Typical uses are parsing newline-delimited JSON, compressing, encrypting, or redacting data as it flows through a pipeline.

64.

What is a Buffer in Node.js?

Fresher

A Buffer is a fixed-length chunk of raw binary data held outside the V8 heap, used for file contents, network packets and anything that is not text. It behaves like an array of bytes and is a subclass of Uint8Array. You need it because JavaScript strings are UTF-16 text and cannot safely represent arbitrary binary data.

65.

How do Buffers relate to strings and encodings?

2–5 yrs

buf.toString(encoding) decodes bytes into a string and Buffer.from(str, encoding) does the reverse, with utf8 the default and hex, base64 and latin1 commonly used. The subtle bug is that a multi-byte UTF-8 character can be split across two chunks of a stream, so decoding each chunk independently produces corruption — use string_decoder or set the stream encoding so partial characters are buffered.

66.

What is the difference between Buffer.alloc and Buffer.allocUnsafe?

Senior

Buffer.alloc zero-fills the memory before returning it; Buffer.allocUnsafe skips that step and is faster, but the buffer may contain whatever was previously in that memory. That old data can include fragments of other requests, so returning an unfilled allocUnsafe buffer to a client is a genuine information disclosure risk. Only use allocUnsafe when you immediately overwrite the entire buffer.

67.

What is the difference between on() and once() on an EventEmitter?

Fresher

on() registers a listener that runs every time the event is emitted; once() registers one that runs at most once and then removes itself. once is the right choice for one-shot lifecycle events such as a connection opening or a stream finishing, and it prevents the leak of a listener that accumulates on every retry. There is also an events.once() helper that returns a promise so you can await an event.

68.

What does the MaxListenersExceededWarning mean?

2–5 yrs

An EventEmitter warns when more than ten listeners are added for the same event, because that usually indicates a leak — a listener registered inside a request handler or a loop and never removed. The warning is a diagnostic, not a hard limit; if you genuinely need more, raise it with setMaxListeners on that emitter rather than globally. Treat it as a prompt to check that you are calling removeListener or using once.

69.

What happens if an EventEmitter emits an error event with no listener?

2–5 yrs

Node treats it specially: an error event with no registered listener is thrown as an exception, which normally crashes the process. This is deliberate, so that async failures cannot be silently ignored the way an unchecked callback error can. Every emitter you keep for any length of time — sockets, streams, database clients — needs an error handler attached.

70.

Are EventEmitter listeners called synchronously or asynchronously?

Senior

Synchronously. emit() invokes each registered listener in order, in the same tick, and returns only after they have all run, so a slow listener blocks the emitter and a thrown error propagates back to the emit call site. If you need asynchronous behaviour, schedule the work yourself with setImmediate or queueMicrotask inside the listener.

Express & REST APIs

71.

What is middleware in Express?

Fresher

Middleware are functions that have access to the request and response objects and the next function in the request-response cycle. They can run code, modify req/res, end the request, or call next() to pass control to the next middleware. They are used for tasks like logging, authentication, body parsing, and error handling, and run in the order they are registered.

72.

What is Express and why is it used?

Fresher

Express is a minimal, unopinionated web framework for Node that sits on top of the built-in http module. It gives you routing, a middleware pipeline, and helpers such as res.json and res.status, so you are not parsing URLs and writing headers by hand. Its minimalism is the trade-off: you assemble validation, auth and error handling from libraries yourself, which is why teams sometimes prefer a batteries-included framework instead.

73.

In what order does Express run middleware?

2–5 yrs

Strictly in registration order, matching on path and method, with each one deciding whether to call next(). That is why body parsers, CORS and logging must be registered before the routes that need them, and why a 404 handler must come last. If a middleware neither ends the response nor calls next(), the request hangs forever with no error — the most common Express bug there is.

74.

What is error-handling middleware in Express?

2–5 yrs

It is a middleware function with four parameters — (err, req, res, next) — which is how Express identifies it. Express skips it in the normal flow and only invokes it when something calls next(err) or throws synchronously in a handler. Register it after all routes, use it to log and to map errors to a status code and a safe response body, and never leak stack traces to clients in production.

75.

How do you handle an error thrown inside an async Express route?

Senior

In Express 4 a rejected promise in an async handler is not caught by the framework — it becomes an unhandled rejection and the request hangs. You either wrap every async handler in a helper that attaches .catch(next), or use a library that does it for you. Express 5 forwards rejections to the error middleware automatically, which is one of the strongest reasons to upgrade.

76.

What is the difference between app.use and app.get?

Fresher

app.get registers a handler for GET requests matching a path exactly. app.use registers middleware that matches any HTTP method and treats the path as a prefix, so app.use('/api', router) matches everything under /api. That prefix matching is what makes app.use the right tool for mounting routers and cross-cutting middleware, and the wrong tool for defining a single endpoint.

77.

Where do route parameters, query strings and the body live on the request?

Fresher

Named path segments such as /users/:id arrive on req.params, everything after the question mark arrives on req.query, and the parsed request body arrives on req.body. req.body is undefined until a body-parsing middleware such as express.json() has run, which is the usual cause of "cannot read property of undefined" in a POST handler. Treat all three as untrusted input and validate them.

78.

What is express.Router used for?

Fresher

Router creates a mountable, isolated set of routes and middleware — effectively a mini application — that you attach with app.use at a path prefix. It keeps a large API organised by resource, lets you apply middleware to a whole group (such as auth on every /admin route), and makes routes easier to test in isolation. Without it, a real API degenerates into one enormous file.

79.

What makes an API RESTful?

2–5 yrs

REST models the system as resources identified by URLs, manipulated through a uniform set of HTTP methods, with each request carrying everything needed to process it — that is, the server keeps no client session state between calls. Responses should use meaningful status codes and be cacheable where appropriate. In practice most "REST" APIs are resource-oriented HTTP rather than strictly RESTful, and saying so shows you understand the distinction.

80.

What HTTP status codes should a REST API return?

Fresher

200 for a successful read or update, 201 with a Location header for a created resource, 204 for a successful call with no body. 400 for malformed input, 401 for missing or invalid credentials, 403 for authenticated but not allowed, 404 for a resource that does not exist, 409 for a conflict such as a duplicate, 422 for semantically invalid input, and 429 when rate limited. 500 for an unexpected server fault and 503 when a dependency is down.

81.

What is the difference between PUT and PATCH?

Fresher

PUT replaces the entire resource with the representation you send, so omitted fields should be cleared, and it is idempotent — sending it twice leaves the same state. PATCH applies a partial update to only the fields provided. POST, by contrast, is neither idempotent nor a replacement; it creates a new subordinate resource.

82.

How do you implement pagination in a REST API?

2–5 yrs

Offset pagination uses limit and offset or page numbers — simple, allows jumping to a page, but gets slow on large offsets and can skip or repeat rows when data changes between requests. Cursor pagination returns an opaque cursor based on a stable sort key, which is efficient and consistent but only supports next and previous. Always cap the page size server-side and return the total or a next cursor so the client knows when to stop.

Authentication & Security

83.

What is the difference between authentication and authorization?

Fresher

Authentication answers "who are you" — verifying credentials such as a password, token or certificate. Authorization answers "what are you allowed to do" — checking roles, ownership or permissions on the specific resource. They fail differently in HTTP: unauthenticated is 401, authenticated but not permitted is 403. A very common bug is authenticating the user and then forgetting to check that the record they requested actually belongs to them.

84.

How does JWT authentication work?

2–5 yrs

A JSON Web Token has three base64url parts — header, payload and signature — joined by dots. On login the server builds a payload of claims (such as user id and expiry) and signs it with a secret or private key; the client sends it back on each request, usually in an Authorization: Bearer header, and the server verifies the signature and expiry. The payload is only encoded, not encrypted, so anyone can read it — never put secrets in it.

85.

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

2–5 yrs

Sessions store state on the server (in memory, Redis or a database) and give the client an opaque cookie id, so revoking a session is a single delete. JWTs are stateless — the server verifies a signature instead of a lookup, which scales horizontally without shared session storage, but you cannot easily revoke a token before it expires. The usual compromise is short-lived access tokens plus a stored, revocable refresh token.

86.

Where should a JWT be stored on the client?

Senior

localStorage is readable by any JavaScript on the page, so a single XSS flaw exfiltrates the token. An httpOnly, Secure, SameSite cookie is not reachable from JavaScript, which removes that class of theft but reintroduces CSRF, so you pair it with SameSite and a CSRF token for state-changing requests. There is no option with no trade-off; the defensible answer names the threat model you are choosing.

87.

How do you handle JWT expiry and refresh tokens?

Senior

Issue a short-lived access token — minutes — and a long-lived refresh token that is stored server-side so it can be revoked, and kept in an httpOnly cookie. When the access token expires the client exchanges the refresh token for a new pair. Rotate the refresh token on every use and invalidate the whole family if an already-used one is presented, which detects and shuts down a stolen token.

88.

How should passwords be stored in a Node.js application?

Fresher

Never in plain text and never with a fast hash like MD5 or SHA-256. Use a deliberately slow, salted password hash — bcrypt, scrypt or Argon2 — with a work factor tuned so a single hash takes a meaningful fraction of a second on your hardware. The salt defeats rainbow tables and the cost factor makes brute forcing expensive; bcrypt generates and stores the salt inside the hash string for you.

89.

What is CORS and how do you configure it in Express?

2–5 yrs

CORS is a browser mechanism that decides whether a page from one origin may read a response from another, enforced through Access-Control-Allow-* headers the server sends. In Express you use the cors middleware, giving it an explicit list of allowed origins, methods and headers, and credentials: true if cookies are involved. Setting the allowed origin to a wildcard while also allowing credentials is both invalid and a sign the configuration has not been thought through.

90.

What does helmet do?

2–5 yrs

Helmet is Express middleware that sets a group of security-related response headers with sensible defaults: Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy, and it removes the X-Powered-By header that advertises Express. It is one line of setup and closes a set of easy attacks, but it is a hardening layer, not a substitute for input validation or authorization.

91.

How do you protect a Node API against brute force and abuse?

2–5 yrs

Apply rate limiting — a fixed or sliding window per IP and per account, with a shared store such as Redis so the limit holds across instances — and return 429 with Retry-After. Add stricter limits and progressive delays on login and password reset, lock or challenge after repeated failures, and cap request body size. Remember that IP-based limits alone are weak behind NAT and CDNs, so key on the authenticated user where you can.

92.

What is SQL injection and how do you prevent it in Node.js?

Fresher

SQL injection is when attacker-controlled input is concatenated into a query and changes its meaning, letting someone read or destroy data. Prevent it by never building SQL with string concatenation: use parameterised queries or prepared statements, where the driver sends the query and the values separately, or an ORM or query builder that does. Escaping by hand and validating input are useful defences in depth but are not the fix.

93.

What is NoSQL injection?

Senior

In document databases, a JSON body can smuggle query operators instead of values — sending { "password": { "$ne": null } } to a login endpoint makes the comparison match any user. It happens because req.body fields are objects, not strings, and are passed straight into the query. Defend by validating and coercing input types with a schema validator, and by using the driver options that reject operator keys in user input.

94.

How do you keep secrets out of a Node.js codebase?

2–5 yrs

Load them from environment variables or a secrets manager at runtime, keep .env files out of version control with .gitignore, and commit a .env.example listing the names with no values. Never log the whole config object or the request headers containing an Authorization value. If a secret does reach the repository, rotating it is mandatory — deleting the commit does not un-publish it.

Scaling, Clustering & Child Processes

95.

What is the difference between clustering and worker threads?

2–5 yrs

The cluster module forks multiple Node processes that share the same server port, letting you use all CPU cores by running separate instances with their own memory and event loop — good for scaling I/O-bound servers. Worker threads run JavaScript in parallel threads within a single process and can share memory, which is better suited for CPU-intensive tasks without blocking the main event loop.

96.

How does the cluster module distribute incoming connections?

Senior

The primary process creates the listening socket and, by default on most platforms, accepts connections itself and hands them to workers round-robin, which spreads load evenly. The alternative is to let the operating system decide which worker accepts, which is faster but can leave load badly skewed. You choose with the schedulingPolicy setting, and round-robin is the default everywhere except Windows.

97.

What cannot be shared between cluster workers?

Senior

Anything held in process memory: in-memory sessions, caches, rate-limit counters, WebSocket connection maps, setInterval-based schedulers and module-level state. Each worker is a separate process with its own heap, so a request handled by worker two cannot see what worker one stored. Move that state into Redis or a database, and use a distributed lock or a single dedicated worker for jobs that must run once.

98.

When should you use worker threads instead of clustering?

2–5 yrs

Use worker threads when the problem is CPU-bound work inside one logical service — parsing huge payloads, image manipulation, compression, cryptography — because threads start faster than processes and can share memory through SharedArrayBuffer without copying. Use clustering when the problem is throughput of independent requests and you simply want to use every core. Worker threads do not help an I/O-bound server at all.

99.

How do worker threads share data with the main thread?

Senior

By default, values sent through postMessage are structured-cloned, which copies them. For large binary payloads you can transfer an ArrayBuffer, which moves ownership with no copy but leaves the sender's reference detached. For genuinely shared state, use SharedArrayBuffer with Atomics for coordination — and remember that ordinary objects, closures and class instances with methods cannot be shared.

100.

What is the difference between spawn, exec, execFile and fork?

2–5 yrs

spawn launches a command and streams its stdout and stderr, so it suits long-running or high-output processes. exec runs the command through a shell and buffers the whole output into memory, which is convenient but has a size limit and a shell-injection risk. execFile is like exec without the shell, so it is safer. fork is a specialisation of spawn that starts a new Node process with an IPC channel already wired up.

101.

When would you use spawn instead of exec?

2–5 yrs

Use spawn when the output could be large or you want to process it as it arrives, because exec buffers everything and fails once maxBuffer is exceeded. spawn also avoids the shell by default, so user-supplied arguments cannot inject additional commands. exec is fine for a short command with small, known output where the shell features are actually wanted.

102.

How do a parent and a forked child process communicate?

2–5 yrs

child_process.fork sets up an IPC channel, so the parent calls child.send(message) and listens with child.on('message'), while the child uses process.send and process.on('message'). Messages are JSON-serialised, so functions and class instances do not survive the trip. It is the same mechanism the cluster module uses between the primary and its workers.

103.

Why put a message queue in front of Node workers?

Senior

A queue decouples accepting work from doing it, so a traffic spike lengthens the queue instead of timing out requests, and slow jobs stop occupying HTTP connections. It also gives you retries with backoff, dead-letter handling for poison messages, and the ability to scale consumers independently of the API. The costs are eventual consistency, at-least-once delivery — so handlers must be idempotent — and another system to operate.

104.

How do you scale a Node.js application horizontally?

2–5 yrs

Keep the process stateless, run many instances behind a load balancer, and push all shared state — sessions, cache, rate limits, uploads — into Redis, a database or object storage. Use sticky sessions only where the protocol requires it, such as raw WebSockets without a shared adapter. In containers you usually run one Node process per container and let the orchestrator handle replica count rather than clustering inside the container.

Performance, Memory & Debugging

105.

What causes memory leaks in Node.js?

Senior

The usual suspects are unbounded caches and Maps that are never evicted, event listeners added per request and never removed, timers and intervals that are never cleared, closures capturing large objects, and global arrays used as buffers. Because the reference is still reachable, garbage collection cannot help. The symptom is a heap that grows steadily and never returns to baseline after a full collection.

106.

How do you find a memory leak in a Node.js process?

Senior

Take heap snapshots with the inspector — via --inspect and Chrome DevTools, or programmatically with v8.writeHeapSnapshot — before and after repeating the suspected work, then compare retained sizes to see which constructor is growing. Sort by retained size rather than shallow size, and follow the retainer path to find what is still holding the objects. Watch process.memoryUsage().heapUsed over time to confirm the leak is real rather than normal heap growth before a collection.

107.

What is the default heap limit in Node.js and how do you change it?

Senior

V8 caps the old-space heap — historically around 1.5 to 2 GB on 64-bit, and on modern Node it is derived from available system memory. You raise it with --max-old-space-size in megabytes, either on the command line or via NODE_OPTIONS. Raising it is a stopgap: if a process needs several gigabytes to serve requests, the real fix is usually streaming instead of buffering, or paginating a query that loads an entire table.

108.

How does garbage collection work in V8?

Senior

V8 uses a generational collector. New objects go into a small young generation collected very frequently by a fast scavenger, on the theory that most objects die young; survivors are promoted to the old generation, which is collected by a mark-and-sweep-and-compact collector that runs less often but costs more. Much of that work is incremental and concurrent, but major collections still introduce pauses, which is why allocating heavily in a hot path shows up as latency spikes.

109.

How do you profile CPU usage in a Node.js application?

Senior

Start the process with --inspect and record a CPU profile in Chrome DevTools, use --cpu-prof to write a profile to disk on exit, or use the inspector module to start and stop profiling programmatically in production. Read the result as a flame graph and look for wide frames, which mean time spent, rather than deep ones. Profile under realistic load — a profile of an idle service tells you nothing.

110.

What is the difference between a heap snapshot and a CPU profile?

Senior

A heap snapshot is a point-in-time map of every object in memory and what retains it, which answers "what is using memory and why is it still reachable". A CPU profile is a time-based sample of which functions were executing, which answers "where is time going". Memory growth calls for snapshots and comparisons; slow responses call for a profile or a flame graph.

111.

How do you debug a Node.js application?

Fresher

Run it with --inspect (or --inspect-brk to pause on the first line) and attach Chrome DevTools or an editor debugger to set breakpoints, step through code and inspect variables. Editors such as VS Code launch this for you through a launch configuration. console.log is fine for a quick check, but a debugger is far faster once the bug involves more than one function, and structured logs are what you want in production.

112.

What are the common ways to speed up a slow Node.js API?

2–5 yrs

Measure first, then work down the list: fix N+1 queries and add the missing database indexes, cache expensive reads, run independent calls in parallel with Promise.all, stream large responses instead of buffering, enable HTTP keep-alive on outbound requests, and move any CPU-heavy work off the event loop. Check event loop delay too — a synchronous hot spot makes every endpoint slow at once, which looks like a database problem but is not.

113.

Where would you add caching in a Node.js API?

2–5 yrs

At several layers: HTTP caching headers and a CDN for public responses, a shared cache such as Redis for expensive computed results and session data, and a small in-process cache for data that is hot and rarely changes. In-process caching is fastest but is per-instance and multiplies memory across replicas. The hard part is always invalidation — prefer short TTLs and explicit invalidation on write over hoping stale data will not matter.

Testing, Config & Production

114.

How do you test a Node.js application?

Fresher

Use a test runner — the built-in node:test, or Jest, Vitest or Mocha — with unit tests for pure logic, integration tests that exercise routes against a real or containerised database, and a smaller number of end-to-end tests. Keep tests independent and deterministic, avoid sharing mutable state between them, and run them in CI on every change. Supertest is the usual way to make requests against an Express app without binding a port.

115.

What is the difference between unit, integration and end-to-end tests?

Fresher

Unit tests exercise one function or module with its dependencies replaced, so they are fast and pinpoint failures but prove nothing about wiring. Integration tests run several real components together — a route, its service and a real database — catching the mistakes unit tests miss. End-to-end tests drive the whole system as a user would; they are the most convincing and the slowest and flakiest, so you keep few of them.

116.

How do you mock an external HTTP call in a Node.js test?

2–5 yrs

Intercept at the network layer with a library such as nock or Node's own MockAgent from undici, so your code under test still runs its real request logic. Injecting the client as a dependency and passing a fake is the other clean option. Avoid mocking so deeply that the test passes while the real request would fail — and add one contract or smoke test against the real service to catch drift.

117.

How do you manage environment-specific configuration?

Fresher

Read configuration from environment variables so the same build artifact runs in every environment, and validate them at startup with a schema so a missing or malformed value fails fast instead of surfacing as a strange error hours later. Keep defaults for local development only, never for production credentials. NODE_ENV should be exactly "production" in production, because many libraries change behaviour on it.

118.

What is dotenv and why should .env files not be committed?

Fresher

dotenv loads key-value pairs from a .env file into process.env for local development, so you do not export variables by hand; modern Node can also do it natively with --env-file. The file typically holds real credentials, so committing it publishes them to everyone with repository access and to anyone who later clones a fork. Gitignore it and commit a .env.example with the keys and dummy values instead.

119.

What is PM2 and what does it give you?

2–5 yrs

PM2 is a process manager for Node that restarts a crashed process, runs several instances in cluster mode across cores, handles log rotation, and can reload with zero downtime. It is a good fit on a plain virtual machine. In a container orchestration environment much of that is redundant — the platform already restarts and scales containers — so running one process per container is usually the cleaner setup.

120.

What is a graceful shutdown and how do you implement one?

Senior

On SIGTERM, stop accepting new connections with server.close(), let in-flight requests finish within a timeout, close database pools and message consumers, then exit zero — with a forced exit after a deadline so a stuck request cannot block the deploy forever. Without it, a rolling deploy kills requests mid-flight and can leave transactions half-applied. In Kubernetes you also fail the readiness probe first, so traffic drains before the process starts closing.

121.

How should logging work in a production Node.js service?

2–5 yrs

Emit structured JSON with a fast logger such as pino or winston, write to stdout and let the platform collect it, and include a request or trace id on every line so one request can be reconstructed across services. Use levels deliberately and make the level configurable per environment. Never log passwords, tokens, full request headers or personal data, and sample or rate-limit high-volume debug lines so logging does not become the bottleneck.

122.

What should a health check endpoint actually check?

2–5 yrs

Separate liveness from readiness. Liveness should be cheap and only prove the event loop is responsive, because a heavyweight check causes restart storms when a dependency wobbles. Readiness may check that critical dependencies — database, cache, queue — are reachable, so the instance is removed from the load balancer instead of being killed. Never make either check hit every downstream service on every poll.

Get these answered live in your real interview

NostrobeAI is a real-time AI interview copilot — it hears the question and drafts a strong answer on your screen, invisible on Zoom, Meet, and Teams. One-time pricing, no subscription.

Try NostrobeAI free