Home / Articles

Node.js and Express Interview Prep: 70 Questions With Precise Answers

A structured review of 70 Node.js and Express interview questions, from the event loop and streams to middleware, security and scaling, with corrections to common answers.

Publisher: REACTAPP.TOP21 min read4861 words

This article is published in English.

Sources: Node.js & Express.js Interview Guide

Backend interviews for Node.js roles tend to circle the same territory: how a single-threaded runtime serves thousands of connections, what the event loop actually does, and how Express turns a raw HTTP server into an application. Memorised one-liners fall apart under a follow-up question, so this review groups 70 common questions by topic and gives each answer enough reasoning that you can defend it. Where the popular answer is outdated or imprecise, the correction is noted, because spotting those details is often what separates a strong candidate from a prepared one.

Node.js fundamentals

Q1. What is Node.js?

It is a runtime that executes JavaScript outside the browser. It embeds Google's V8 engine and adds APIs for files, networking and processes. Its model is event-driven with non-blocking input/output, which keeps it light and well suited to data-heavy and real-time services.

Q2. Is Node.js single-threaded?

Your JavaScript runs on one main thread, the one that drives the event loop. Underneath, libuv keeps a thread pool for work that has no good asynchronous OS interface, such as file system calls, dns.lookup, some crypto functions and zlib. So the accurate answer is: single-threaded for your code, multi-threaded at the system level.

Q3. Why is Node.js fast?

  • V8 compiles JavaScript to machine code instead of interpreting it.
  • Input and output are asynchronous and non-blocking.
  • The event loop provides concurrency without a thread per request.
  • The model shines for network- and disk-bound work; CPU-bound work is its weak spot.

Q4. Synchronous versus asynchronous code

A synchronous operation finishes before the next line runs, blocking everything behind it. An asynchronous operation, such as a file read or a database query, is started and handed off; execution continues, and a callback or promise delivers the result later. In the synchronous version below, the log waits for the entire read (and prints a Buffer, since no encoding is passed):

// Synchronous
const data = fs.readFileSync('file.txt');
console.log(data);

In the asynchronous version, the final log line prints before the file contents, because the read completes on a later turn of the event loop:

// Asynchronous
fs.readFile('file.txt', (err, data) => {
  console.log(data);
});
console.log("This runs first!");

Q5. What is npm?

npm is the package manager that ships with Node.js. You use it to install, publish and manage dependencies, and it records them, along with scripts, in package.json.

Q6. package.json versus package-lock.json

package.json describes the project: its name, version, entry point, scripts and the dependency ranges it accepts. package-lock.json pins the exact resolved version of every installed package, transitive ones included, so that every machine and CI run installs an identical tree. Commit both.

Q7. Which core modules should you know?

Commonly cited built-ins include fs, path, http and https, os, events, stream, buffer, crypto, util, url, querystring (now considered legacy in favour of URLSearchParams), child_process, cluster and zlib.

Q8. require() versus import

The differences between CommonJS and ES modules come up constantly:

  • require loads synchronously; ES modules are loaded asynchronously and also offer dynamic import() that returns a promise.
  • CommonJS is the default for .js files; ES modules need "type": "module" in package.json or the .mjs extension.
  • CommonJS exports with module.exports; ES modules use export and export default.
  • require can be called anywhere, including inside conditions; static import declarations are hoisted and must be at the top level.

Q9. module.exports versus exports

exports starts out as a reference to the same object as module.exports. Adding properties to it works, but assigning a new value (exports = {...}) only rebinds the local variable, and the module still exports the original object. When replacing the whole export, assign to module.exports.

Q10. What is the REPL?

REPL stands for Read, Eval, Print, Loop. Running node with no arguments opens an interactive shell that evaluates each expression you type and prints the result, which is handy for quick experiments.

The event loop and asynchronous patterns

Q11. What is the event loop?

It is the mechanism that lets a single JavaScript thread perform non-blocking input/output. When the call stack is empty, the loop takes the next ready callback from its queues and runs it. A deeper treatment of how it interacts with libuv lives in our article on libuv, the event loop and the thread pool.

Q12. What are the phases of the event loop?

  1. Timers: callbacks from setTimeout and setInterval whose time has come.
  2. Pending callbacks: certain system-operation callbacks deferred from the previous iteration.
  3. Idle and prepare: internal housekeeping.
  4. Poll: collects newly completed input/output events and runs their callbacks.
  5. Check: setImmediate() callbacks.
  6. Close callbacks: handlers such as socket.on('close').

The microtask queues (process.nextTick first, then promise reactions) are drained after each individual callback, not only between phases. That has been the behaviour since Node 11, and it is a common follow-up question.

Q13. nextTick, promises, setTimeout and setImmediate

  • process.nextTick() callbacks run as soon as the current operation finishes, ahead of promise microtasks.
  • Promise reactions such as .then run once the nextTick queue is empty, still before the loop moves on.
  • setTimeout(fn, 0) fires during the timers phase once its minimum delay has passed.
  • setImmediate() fires during the check phase, right after the poll phase of the same iteration.

This script exercises all four:

console.log('start');
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
console.log('end');

The expected output shows the synchronous logs first, then the microtasks. The two macrotasks can appear in either order when scheduled from the main module, because it depends on whether the timer threshold has elapsed when the loop starts; inside a file or socket callback, setImmediate always wins:

// Output:
// start
// end
// nextTick
// promise
// setTimeout (or setImmediate — order can vary if not inside I/O)
// setImmediate (or setTimeout)

Q14. Callbacks and callback hell

A callback is a function you pass to an operation so it can be called when that operation completes. When each step depends on the previous one, callbacks nest deeper and deeper, producing the pyramid known as callback hell, which is hard to read and to handle errors in:

getUser(id, (user) => {
  getPosts(user, (posts) => {
    getComments(posts, (comments) => {
      // deeply nested - hard to manage
    });
  });
});

Promises and async/await flatten this structure.

Q15. What is a Promise?

A Promise is an object standing in for the future result of an asynchronous operation. It is either pending, fulfilled with a value, or rejected with a reason, and once settled it never changes state. You attach handlers with .then and .catch:

const promise = new Promise((resolve, reject) => {
  if (success) resolve(data);
  else reject(error);
});
promise.then(data => console.log(data)).catch(err => console.error(err));

Q16. What is async/await?

It is syntax built on promises that lets asynchronous code read top to bottom. An async function always returns a promise, and await pauses that function until the awaited promise settles, with rejections surfacing as exceptions you can catch:

async function getUser() {
  try {
    const res = await fetch('/api/user');
    const data = await res.json();
    return data;
  } catch (err) {
    console.error(err);
  }
}

Note that this example logs the error and implicitly returns undefined, so callers cannot tell failure from an empty result. Rethrowing is often the better choice.

Q17. Promise combinators

  • Promise.all() fulfils when every input fulfils and rejects as soon as any input rejects.
  • Promise.race() settles with whichever input settles first, fulfilled or rejected.
  • Promise.allSettled() waits for every input and reports each outcome.
  • Promise.any() fulfils with the first fulfilled input and rejects (with an AggregateError) only if all reject.

Q18. Blocking versus non-blocking

Blocking code stops everything else until it finishes, as fs.readFileSync does. Non-blocking code starts the work and lets the program continue, as fs.readFile does. On a server, one blocking call stalls every in-flight request.

Streams, buffers and events

Q19. What are streams?

Streams process data incrementally, chunk by chunk, instead of holding it all in memory. They are the right tool for large files, media and network traffic. There are four kinds:

  • Readable, such as fs.createReadStream
  • Writable, such as fs.createWriteStream
  • Duplex, readable and writable at once, such as a TCP socket
  • Transform, a duplex stream that changes data as it passes, such as zlib.createGzip

Q20. What is a Buffer?

A Buffer holds raw binary data in memory allocated outside the V8 heap. You meet it wherever data is bytes rather than strings or objects: streams, files, sockets. Creating one from a string shows the underlying byte values in hexadecimal:

const buf = Buffer.from('Hello');
console.log(buf); // <Buffer 48 65 6c 6c 6f>

Q21. What is EventEmitter?

EventEmitter from the events module implements the observer (pub/sub) pattern that much of Node's API is built on. You create an instance:

const EventEmitter = require('events');
const emitter = new EventEmitter();

then register listeners with on and trigger them with emit, passing any arguments along:

emitter.on('greet', (name) => console.log(`Hello, ${name}`));
emitter.emit('greet', 'John');

Listeners run synchronously, in registration order, when emit is called.

Q22. How do you process large data without running out of memory?

Use streams: read small chunks and pipe a readable stream into a writable one, so memory use stays roughly constant regardless of file size:

fs.createReadStream('input.txt').pipe(fs.createWriteStream('output.txt'));

For production code, stream.pipeline is preferable to .pipe() because it propagates errors and cleans up every stream in the chain.

Deeper runtime concepts

Q23. process.nextTick() versus setImmediate(), in depth

nextTick callbacks run before the loop is allowed to continue, even before promise microtasks. setImmediate callbacks wait for the check phase, after the poll phase. Because each nextTick can schedule another, a recursive chain can starve the loop so that pending reads and writes never get serviced. That failure mode is explored in how process.nextTick silently starves the event loop.

Q24. What is clustering and why use it?

A single Node process uses one core for JavaScript. The cluster module forks several worker processes, each with its own event loop and memory, and lets them share one server port so the machine's cores all serve traffic. You import the modules:

const cluster = require('cluster');
const os = require('os');

then fork one worker per CPU from the primary process, while workers load the server:

if (cluster.isMaster) {
  os.cpus().forEach(() => cluster.fork());
} else {
  require('./server');
}

cluster.isMaster still works but has been deprecated in favour of cluster.isPrimary; use the latter in new code.

Q25. cluster versus worker_threads

  • cluster creates separate processes with isolated memory, a good fit for scaling HTTP servers.
  • worker_threads creates threads inside one process. They can share memory through SharedArrayBuffer or transfer buffers, which suits CPU-heavy work such as image processing.

Q26. How are CPU-heavy tasks handled?

CPU-bound code blocks the event loop, so you move it elsewhere: to worker_threads, to a child process, or out of the service entirely via a queue and background workers. See offloading CPU work with worker threads and pools for patterns.

Q27. What is middleware in general?

Middleware is code that sits between receiving a request and sending the response, where it can inspect, transform, validate or log. Express builds its entire design around this idea, as the Express sections below show.

Q28. What are child processes?

The child_process module runs external programs or other Node scripts in separate processes. Four functions matter:

  • spawn() streams output, so it handles large outputs well.
  • exec() runs through a shell and buffers output, fine for small results.
  • execFile() runs a file directly without a shell, which is safer with untrusted arguments.
  • fork() is a specialised spawn for Node modules that sets up an IPC channel for messages.

Q29. What is libuv?

libuv is the C library that provides Node's event loop, asynchronous input/output and thread pool, hiding the differences between Linux, macOS and Windows event notification mechanisms.

Q30. setTimeout versus setInterval

setTimeout(fn, delay) runs fn once after at least delay milliseconds. setInterval(fn, delay) runs it repeatedly at that interval until you call clearInterval().

Q31. Handling uncaught exceptions and unhandled rejections

Process-level handlers exist for both. For synchronous exceptions nobody caught, log and exit, because the process may be in an inconsistent state:

process.on('uncaughtException', (err) => {
  console.error('Uncaught Exception:', err);
  process.exit(1); // recommended: exit after cleanup
});

For promises rejected without a handler:

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled Rejection:', reason);
});

Since Node 15, an unhandled rejection crashes the process by default, so registering this listener changes that behaviour. Treat both as last-resort logging, not as control flow; use try/catch and .catch() in the code itself.

Q32. What is a memory leak and how do you find one?

A leak is memory that stays reachable after it is no longer needed: growing global caches, timers or listeners never removed, closures holding large objects. Diagnose with the --inspect flag and Chrome DevTools heap snapshots, or tools such as heapdump and clinic.js, and compare snapshots over time.

Q33. __dirname versus __filename

__dirname is the absolute path of the folder containing the current module; __filename is the absolute path of the file itself. Both exist only in CommonJS. In ES modules, derive them from import.meta.url, or use import.meta.dirname and import.meta.filename on recent Node versions.

Q34. Why use the path module?

path builds and parses file paths portably through functions such as path.join, path.resolve, path.basename and path.extname, so you never hand-concatenate strings and trip over Windows backslashes versus Unix slashes.

Q35. fs.readFile versus fs.createReadStream

fs.readFile loads the whole file into memory before calling back, which is risky for large files. fs.createReadStream delivers the file in chunks, keeping memory flat and allowing processing to start immediately.

Express essentials

Q36. What is Express?

Express is a minimal, unopinionated web framework on top of Node's http module. It adds routing, a middleware pipeline and template engine integration, removing most of the boilerplate of raw request handling.

Q37. A basic Express server

Create an application object:

const express = require('express');
const app = express();

then register a route and start listening:

app.get('/', (req, res) => {
  res.send('Hello World');
});app.listen(3000, () => console.log('Server running on port 3000'));

Q38. What is middleware in Express?

An Express middleware is a function receiving req, res and next. It can run any code, modify the request or response, end the cycle by responding, or call next() to hand control to the following function. A request logger is the classic example:

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

If a middleware neither responds nor calls next(), the request hangs.

Q39. Kinds of Express middleware

  1. Application-level, attached with app.use() or app.METHOD().
  2. Router-level, attached to an express.Router() instance.
  3. Error-handling, recognised by its four parameters (err, req, res, next).
  4. Built-in, such as express.json() and express.static().
  5. Third-party, such as cors, morgan and helmet.

Q40. How does routing work?

A route pairs an HTTP method with a path and a handler. A typical CRUD resource looks like this:

app.get('/users', (req, res) => { /* get all users */ });
app.post('/users', (req, res) => { /* create user */ });
app.put('/users/:id', (req, res) => { /* update user */ });
app.delete('/users/:id', (req, res) => { /* delete user */ });

Q41. What is express.Router()?

A router is a self-contained mini application with its own routes and middleware, which you mount on a path. It keeps large apps organised by feature. Define routes in their own module:

// userRoutes.js
const router = require('express').Router();
router.get('/', (req, res) => res.send('All users'));
module.exports = router;

and mount it in the main app, where its / becomes /users:

// app.js
app.use('/users', require('./userRoutes'));

Q42. app.use() versus app.get() and friends

app.use() matches every HTTP method and any path that starts with the given prefix, which is why it mounts middleware and routers. app.get(), app.post() and similar match one method and the full route pattern.

Q43. req.params, req.query and req.body

  • req.params holds named route segments: /users/:id gives req.params.id.
  • req.query holds query string values: /users?age=25 gives req.query.age, always as a string.
  • req.body holds the parsed request payload for methods like POST and PUT, and stays undefined unless body-parsing middleware such as express.json() runs first.

Q44. Parsing JSON and form data

Two built-in parsers cover most cases, one for JSON bodies and one for URL-encoded HTML forms:

app.use(express.json());               // parses application/json
app.use(express.urlencoded({ extended: true })); // parses form data

Q45. What is express.static()?

It is built-in middleware that serves files such as images, stylesheets, scripts and HTML from a directory:

app.use(express.static('public'));

Intermediate Express

Q46. Error handling

Errors are handled by middleware with four parameters, registered after all routes:

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Something went wrong' });
});

Inside routes and middleware, pass failures to it with next(err):

app.get('/user/:id', async (req, res, next) => {
  try {
    const user = await getUser(req.params.id);
    res.json(user);
  } catch (err) {
    next(err); // passed to error-handling middleware
  }
});

Avoid sending err.stack or internal messages to clients in production.

Q47. Handling 404s

Express has no built-in JSON 404. Add a catch-all middleware after every route; if a request reaches it, nothing else matched:

app.use((req, res) => {
  res.status(404).json({ message: 'Route not found' });
});

Q48. CORS and the cors package

Cross-Origin Resource Sharing is the browser mechanism that decides whether a page from one origin may read responses from another. The browser enforces it; the server opts in through response headers, which the cors package sets for you:

const cors = require('cors');
app.use(cors()); // allow all origins
app.use(cors({ origin: 'https://example.com' })); // restrict origin

The snippet shows two alternatives, allowing every origin or a single one; use one or the other, not both, since the permissive call would apply first.

Q49. Sessions and cookies

Load the cookie and session middleware:

const cookieParser = require('cookie-parser');
const session = require('express-session');

then register them with a signing secret and options:

app.use(cookieParser());
app.use(session({
  secret: 'mySecretKey',
  resave: false,
  saveUninitialized: true
}));

Keep the secret in an environment variable rather than in source, and consider saveUninitialized: false to avoid creating sessions for anonymous visitors. The default in-memory store is not meant for production.

Q50. JWT authentication

Import the library:

const jwt = require('jsonwebtoken');

The code below signs a token containing the user id on login, protects routes with a middleware that extracts the bearer token from the Authorization header and verifies it, and attaches the decoded payload to req.user:

// Generate token on login
const token = jwt.sign({ id: user.id }, 'secretKey', { expiresIn: '1h' });// Middleware to protect routes
function auth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ message: 'No token' });
  try {
    req.user = jwt.verify(token, 'secretKey');
    next();
  } catch (err) {
    res.status(403).json({ message: 'Invalid token' });
  }
}app.get('/protected', auth, (req, res) => res.json({ data: 'secret data' }));

As with sessions, the hard-coded 'secretKey' should come from configuration. Responding 401 for a missing token and 403 for an invalid one is a common convention, although many APIs return 401 for both.

Q51. Request validation

Libraries such as express-validator or joi declare rules and collect errors. With express-validator you import the helpers:

const { body, validationResult } = require('express-validator');

then chain rule middleware before the handler and check the result:

app.post('/user',
  body('email').isEmail(),
  body('password').isLength({ min: 6 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
    // proceed
  }
);

For a deeper comparison of validation approaches, see Zod versus express-validator.

Q52. File uploads

multer parses multipart/form-data. Configure a destination:

const multer = require('multer');
const upload = multer({ dest: 'uploads/' });

and apply it per route; upload.single('file') expects one file in the field named file and exposes it as req.file:

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ file: req.file });
});

In real systems, also limit file size and type, and never trust the client-supplied filename.

Q53. Connecting to MongoDB with Mongoose

Open a connection:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/mydb');

then define a model and use it from a route:

const User = mongoose.model('User', new mongoose.Schema({
  name: String,
  email: String
}));app.post('/users', async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json(user);
});

Passing req.body straight into create lets clients set any schema field, so validate or pick allowed fields first.

Q54. Middleware execution order

Express runs middleware in the order it was registered, top to bottom, and the chain stops at any function that does not call next(). That ordering is why 404 handlers and error handlers go last.

Q55. Template engines

Template engines render HTML from server-side data. Popular choices are EJS, Pug (the template engine formerly known by another name) and Handlebars. Setting one up takes a view engine setting and a res.render call:

app.set('view engine', 'ejs');
app.get('/', (req, res) => res.render('index', { name: 'John' }));

Advanced Express and Node

Q56. Async errors in Express 4 versus Express 5

In Express 4, a rejected promise from an async handler is not caught by the framework; without try/catch plus next(err) or a wrapper, it becomes an unhandled rejection. Express 5 forwards rejected promises from handlers and middleware to the error handler automatically. The usual Express 4 workaround is a small higher-order function:

// Express 4 workaround
const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

Wrapping a handler with it routes any rejection to next:

app.get('/data', asyncHandler(async (req, res) => {
  const data = await fetchData(); // if this throws, asyncHandler catches it
  res.json(data);
}));

The pattern is covered in detail in removing try/catch boilerplate with asyncHandler.

Q57. Structuring a large Express app

A common MVC-style layout separates concerns into folders:

project/
├── controllers/     # business logic
├── routes/          # route definitions
├── models/          # DB schemas
├── middlewares/      # custom middleware
├── config/          # DB, env configs
├── utils/           # helper functions
├── app.js
└── server.js

Routes declare endpoints and delegate to controllers, controllers hold the request logic, and models encapsulate data access. Splitting app.js (configuring the app) from server.js (starting it) also makes the app easy to import in tests.

Q58. Rate limiting

express-rate-limit counts requests per client over a window. Import it:

const rateLimit = require('express-rate-limit');

and apply a limiter, here 100 requests per IP every 15 minutes:

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per window
});app.use(limiter);

The default store is in memory, so each clustered process counts separately; use a shared store such as Redis when you scale out. Behind a proxy, configure trust proxy so the real client IP is used.

Q59. Securing an Express app

  • Set protective HTTP headers with helmet.
  • Validate and sanitise all input to block SQL or NoSQL injection and XSS.
  • Serve over HTTPS.
  • Apply rate limiting.
  • Load secrets from the environment, for example with dotenv, rather than committing them.
  • Keep dependencies patched and run npm audit.
  • Add CSRF protection for cookie-based, form-driven apps.

Q60. res.send() versus res.json() versus res.end()

  • res.send() accepts strings, objects or buffers and picks a suitable Content-Type.
  • res.json() serialises its argument as JSON and sets Content-Type: application/json.
  • res.end() finishes the response without further data, a thin wrapper over Node's own method.

Q61. What does next('route') do?

It skips the remaining callbacks of the current route and jumps to the next route that matches. It only applies to handlers registered through app.METHOD() or router.METHOD() with several callbacks.

Q62. Improving performance

  • Run one process per core with clustering or PM2.
  • Cache responses in Redis or in memory.
  • Compress responses with the compression middleware, or let a proxy do it.
  • Put Nginx or another reverse proxy or load balancer in front.
  • Keep CPU-heavy work off the event loop.
  • Pool database connections.
  • Consider HTTP/2.
  • Use asynchronous input/output throughout and avoid the Sync variants in request paths.

Q63. REST versus GraphQL with Express

REST exposes many endpoints with fixed response shapes, so clients often over-fetch or under-fetch. GraphQL exposes one endpoint where the client describes exactly the data it wants. It has typically been mounted in Express with express-graphql or apollo-server-express; both of those packages have since been deprecated in favour of newer integrations, so check current Apollo or GraphQL Yoga documentation.

Q64. How does Express handle concurrent requests?

Each request is just a series of callbacks on the event loop. Because database and network calls are non-blocking, one thread can interleave many requests: network traffic is delegated to the operating system's async facilities, file work to the libuv thread pool, and callbacks run as results arrive. There is no thread per request as in classic multi-threaded servers, which is also why one slow synchronous handler affects everyone.

Q65. PM2 versus the cluster module

cluster is the built-in primitive for multi-process scaling. PM2 is a process manager built on the same idea that adds automatic restarts after crashes, load balancing, log handling, zero-downtime reloads and monitoring, which is why it appears in so many production deployments.

Q66. Why put a reverse proxy in front of Node?

A reverse proxy such as Nginx terminates TLS, balances load across instances, caches, compresses and serves static files, taking that work off Node so the application process can focus on business logic.

Q67. Environment-specific configuration

The dotenv package loads variables from a .env file into process.env, and the app falls back to defaults when a value is missing:

require('dotenv').config();
const PORT = process.env.PORT || 3000;

Recent Node versions can also load env files natively with the --env-file flag. Never commit .env files that contain secrets.

Q68. app.all() versus app.use()

app.all() matches every HTTP method on one specific route path. app.use() also matches every method, but for the path and everything beneath it, which is why it is the tool for mounting middleware.

Q69. Pagination with Express and MongoDB

Read page and limit from the query string with sensible defaults, then combine skip and limit:

app.get('/posts', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const posts = await Post.find()
    .skip((page - 1) * limit)
    .limit(limit);
  res.json(posts);
});

Harden it before production: pass a radix to parseInt, clamp page to at least 1 and limit to a maximum, and add a stable sort. For very large collections, skip gets slower as the offset grows, and cursor-based pagination on an indexed field scales better.

Q70. What is idempotency?

An operation is idempotent when repeating it leaves the server in the same state as doing it once. GET, PUT and DELETE are defined as idempotent; POST generally is not, because each call can create another resource. Retries after timeouts are safe only for idempotent requests, which is why payment APIs often add idempotency keys to POST.

Key takeaways

  • Most Node questions reduce to one model: a single JavaScript thread, an event loop with defined phases, microtasks drained after every callback, and libuv handling the rest.
  • Most Express questions reduce to another: an ordered chain of middleware where each function either responds or calls next(), with errors flowing to four-argument handlers.
  • Interviewers reward precision on the details that changed over time, such as cluster.isPrimary, crash-on-unhandled-rejection, Express 5 async errors and ESM path helpers.
  • Pair every answer with a production caveat (secrets in config, error propagation, shared rate-limit stores), because that is usually the follow-up question.