88 Node.js interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
Node.js is a runtime that executes JavaScript outside the browser, commonly used for web servers, command-line tools, and API services. This question bank spans the event loop and async model, building servers with the http module and Express middleware, streams and file I/O, child processes, error handling for unhandled rejections, JWT-based security, and performance and caching in production.
and 17 more topics.
Node.js is a runtime that runs JavaScript outside the browser, for servers and command-line tools. It is the same JavaScript on the same V8 engine as Chrome; what differs is which APIs are available and what access you have to the machine. Browser Node.js ---------…
V8 is Google's open-source JavaScript engine, written in C++ and used in Chrome. Node.js embeds V8 to run JavaScript, then adds what V8 lacks: reading files, starting servers, making network calls. V8 runs code through two tiers: - Ignition (interpreter): parses the code, turns it…
Event-driven architecture is a model where control flow is determined by events rather than sequential top-to-bottom execution. In Node.js, you register a handler for an event and the runtime invokes it when the event occurs, instead of blocking to wait. Think of a hotel front…
Non-blocking I/O means Node sends off I/O work (reading a file, querying a DB, calling HTTP) and moves on immediately. When the I/O finishes, the event loop (the loop running your JavaScript on one thread) calls its callback. Line 2 prints before line 3 because…
npm (Node Package Manager) is the package manager that ships with Node.js: it downloads libraries from the npmjs.com registry, records them in package.json, and pins exact versions in package-lock.json. npm install <pkg npm install --save-dev <pkg (or -D) --------- Recorded in dependencies devDependencies For What…
package.json describes a Node.js project: its name, version, the packages it needs, the commands it runs, and how others import it. Field Used for ------ name, version Identify the package; required to publish to npm scripts Commands run with npm run <name, e.g. build, test,…
package-lock.json records the exact version of every installed package, including dependencies of dependencies, along with the download URL and a hash. Commit it so every machine and every CI run installs the same nodemodules. ^2.1.3 means "any 2.x from 2.1.3 up is fine". Without a…
CommonJS (CJS): require() loads synchronously and blocks, so it can sit anywhere — inside an if, inside a function. module.exports accepts any value, at the cost of no tree-shaking. ESM: import must be top-level so bundlers can analyze it statically, loads asynchronously so fetches can…
process is a global object (no require needed) for reading and controlling the running Node process: env, command-line arguments, signals, memory, exit code. The parts you use most: - process.env: reads config like PORT. Validate required variables at startup so a missing one stops the…
dirname is the absolute path to the directory holding the running file, filename the path to the file itself. Both resolve from the file's location, not from process.cwd(). In ES Modules (.mjs files, or type: module in package.json) neither exists. Node 20.11 / 21.2 and…
The event loop is a continuously running cycle that processes callbacks across six phases: 1. Timers — runs setTimeout/setInterval callbacks whose time has come. 2. Pending callbacks — I/O errors left over from the previous turn. 3. Idle/prepare — internal use. 4. Poll — collects…
setTimeout(fn, 0) is really setTimeout(fn, 1) at minimum and runs in the timers phase once the timer has expired. setImmediate() runs in the check phase, right after the poll phase. Outside an I/O context (say, directly in the main script) the order between them is…
A Promise is an object that represents the result of async work that has not finished yet. It has 3 states: pending (waiting), fulfilled (succeeded, has a value) and rejected (failed, has a reason). A promise changes state only once. The four combinators differ in…
async/await is syntactic sugar over Promises — an async function always returns a Promise, and await pauses that function (without blocking the thread) until the Promise settles. try/catch catches a rejected Promise like an ordinary exception: Running sequentially or in parallel decides the total time:…
Buffer is Node's built-in class for binary data: a fixed-length sequence of bytes. A string is a sequence of characters with no access to individual bytes, so reading an image or a network packet needs a Buffer. Buffer is a subclass of Uint8Array. 8 characters…
The http module is built in — nothing to install: - req carries method, url and headers, and is a Readable stream — read the body with req.on('data', chunk = ...). - res is a Writable stream — you must call res.end(), otherwise the client…
Express is a minimal web framework for Node.js. It handles two things: routing (which URL calls which function) and middleware (a chain of functions that process the request in order), and leaves project structure up to you. For GET /users/7, the request goes through the…
Middleware is a function with the signature (req, res, next) that runs in exactly the order it was registered — and that order is absolute. - App-level: app.use(helmet()) applies to the whole app. - Router-level: router.use(authMiddleware) applies only to that group of routes. - Error-handling:…
express.Router() creates a mini-app with its own routes and middleware — the primary way to organize code by feature. - Nested routers: adminRouter.use('/users', adminUserRouter) for deeper nesting. - Versioned APIs: app.use('/api/v1', v1Router) alongside app.use('/api/v2', v2Router) — the two versions are fully independent, so deprecating v1…
In Express, each HTTP method has a function of the same name on app (or a router): app.get, app.post, app.put, app.patch, app.delete. Each takes a path and a handler (req, res); dynamic segments like :id land in req.params, the query string in req.query, and the…
Error handling middleware takes 4 parameters (err, req, res, next) and must be registered LAST, after every route. - AppError separates operational errors (404, validation failure — expected) from programming errors (null reference — actual bugs). - Express 5 forwards async route errors automatically, so…
CORS is a browser mechanism protecting the user — the server must opt in before cross-origin requests are allowed. The browser sends an OPTIONS request first whenever the request is preflighted (a method beyond GET/POST/HEAD, or a non-simple header such as Content-Type: application/json); simple requests…
express.json() parses Content-Type: application/json into a req.body object — built in since Express 4.16, so no separate body-parser package. - express.urlencoded({ extended: true }) parses HTML form data (application/x-www-form-urlencoded). extended: true uses the qs library and accepts nested objects; extended: false uses querystring and stays…
Rate limiting caps how many requests one client may send in a time window, to stop spam and brute-force (for example password guessing on a login endpoint). Fixed window counts requests inside a fixed slot (0:00-0:15) — easy to game by bunching a burst at…
JWT is an open standard (RFC 7519) for carrying information as JSON, signed so its integrity can be verified. The token is three dot-separated parts, each base64url-encoded: - Header — { alg: 'HS256', typ: 'JWT' } - Payload — claims such as { sub: userId,…
Full flow: POST /login verifies credentials, signs two tokens, returns the access token in the response body and the refresh token in an httpOnly cookie. The auth middleware reads the token from the header and must pin the algorithm: - An httpOnly cookie keeps XSS…
bcrypt exists to hash passwords, unlike SHA-256/MD5 which are tuned to be fast. In hashcat's benchmark on an RTX 4090, SHA-256 reaches about 22 billion guesses per second, while bcrypt at cost=5 manages about 184 thousand; each cost step doubles the work, so cost=12 drops…
Cache-aside (lazy loading) is the most common pattern: read cache → miss → query the DB → write cache → return. The example uses ioredis, where setex(key, seconds, value) writes with a TTL; node-redis spells it setEx. The other two trade off differently: write-through writes…
Node.js 14 and earlier only printed a warning and kept running — a silent failure. From Node.js 15 the process crashes with exit code 1 by default; that was a breaking change. The right order: try/catch inside async functions is the primary mechanism, .catch() covers…
fs.readFile reads the whole file into one Buffer before giving you the result. fs.createReadStream returns a Readable stream that reads the file chunk by chunk (64 KiB by default) and hands the chunks to you as they arrive. What happens to RAM: - readFile: a…
fs.readFileSync reads a file synchronously: the main thread waits until the data is there, and meanwhile the event loop cannot handle any other request. fs.readFile reads asynchronously: the read is handed to libuv's thread pool, the main thread carries on, and when the read finishes…
Environment variables separate config from code, following the 12-factor app principles (a set of rules for building apps that are easy to deploy, one of which is that config lives outside the code): config is anything that differs between environments (dev/staging/prod). require('dotenv').config() loads .env into…
Breakpoint debugging: node --inspect app.js exposes the debugger on port 9229; open chrome://inspect to attach. --inspect-brk pauses on the first line — use it when debugging initialization code. - Memory profiling: DevTools → Memory → Heap Snapshot; compare two snapshots to find leaks, and read…
The seven vulnerabilities you hit most often in a Node.js API, and how to close each: - SQL injection: db.query('SELECT FROM users WHERE id = ' + req.params.id) — an attacker passes 1 OR 1=1 and dumps the whole DB. Fix with a parameterized query…
Test pyramid for a Node.js API: unit tests (service/utils logic) → integration tests (controller + a real DB) → E2E tests (full HTTP). Test controllers with supertest — no server to start, since supertest injects the request straight into the app: Test services by mocking…
Node.js is a runtime for running JavaScript outside the browser, built on Chrome's V8 engine. Your code runs on one thread, while waiting on I/O is handed to the libuv library. Send 100 requests at once and all 100 finish just over 100ms later, not…
REST is a style for designing APIs over HTTP: each URL represents a resource (user, order…), and the action on that resource is expressed by the HTTP method. Same URL /users: GET lists, POST creates. URLs are nouns only, no /getUsers. Key principles: - Methods…
For file uploads in Express, multer is the most popular middleware. Configure it: const upload = multer({ dest: 'uploads/' }) and attach to a specific route: app.post('/upload', upload.single('file'), handler). Always validate two things: file size (use limits: { fileSize: 5 1024 1024 } for 5MB)…
Streams process data in small chunks rather than loading everything into memory — critical for large files (multi-GB CSVs, videos). There are 4 types: - Readable: reads data, e.g. fs.createReadStream. - Writable: writes data, e.g. fs.createWriteStream. - Duplex: reads and writes, e.g. a TCP socket.…
process.env is an object containing all operating system environment variables accessible to the Node.js process — e.g., process.env.DATABASEURL for a connection string. The dotenv library reads the .env file at the project root and injects its variables into process.env at startup, so developers don't need…
Zod is a popular choice for Express + TS: schema-first, type-safe, and safeParse returns structured errors. Since Zod 4, email validation is written z.email() (the old z.string().email() is deprecated). After parsing, use result.data instead of req.body: data is correctly typed and by default drops any…
Production logging needs a clear structure and strategy for effective debugging when incidents occur. Use Pino (focused on speed, low overhead) or Winston (more flexible, many transports). Format logs as JSON rather than plain text — JSON is easily parsed by aggregators like ELK Stack,…
Socket.io is a realtime library that runs on top of WebSocket but has its own protocol: every message is wrapped with an event name and metadata. That is why a plain WebSocket client (the browser's new WebSocket(...)) cannot connect to a Socket.io server; both sides…
Node.js runs your JavaScript on one thread, but I/O work (reading files, querying a database, calling HTTP) does not run on that thread. Node just sends the I/O request off, registers a callback, and moves on to other requests. The loop that decides "which callback…
All three are ways to write asynchronous code, and each has its own place to catch errors. A callback receives the error as its first argument (err), a Promise passes the error down to .catch(), and async/await lets you catch it with try/catch like synchronous…
Environment variables store configuration that changes per environment (development, staging, production) — such as database URLs, API keys, and port numbers — without modifying code. The .env file holds these values for local development; the dotenv package automatically loads them into process.env. Since Node 20.6,…
All three live in childprocess and create a child process to run work off the event loop, differing in how you get the result: - spawn(cmd, args): runs a command and returns stdout/stderr as streams. No full-output buffering → good for large data or long-running…
Node's strength (single thread + event loop) is also its weakness: every request shares one JS-executing thread. - CPU-bound work blocks the event loop: a heavy loop or CPU-costly synchronous function blocks the thread, forcing every other request to wait. This is the main reason…
The built-in crypto module wraps OpenSSL and bundles the cryptographic primitives: - Hashing — createHash('sha256'): one-way, keyless digest. Use for checksums/data integrity. - HMAC — createHmac('sha256', key): a keyed hash, proving data came from someone who knows the secret (verifying webhooks, signing tokens). - Symmetric…
Two ways to hold login state, differing in where the state lives: express-session (stateful, server-side) - The server stores the session data; the client only holds a session id in a cookie. - Needs a store (Redis/DB) to scale across instances — don't use the…
- Buffer.alloc(n) allocates n bytes and zero-fills the whole region. - Buffer.allocUnsafe(n) allocates n bytes but leaves them uninitialized — the contents are recycled memory and may still hold old process data. - Buffer.from(...) builds a buffer from existing data: a string, a byte array,…
Both errors are a module-system mismatch: Node treats the file as one system (CommonJS or ESM) while the code is written for the other. - Cannot use import statement outside a module: the file is treated as CommonJS but uses import. - ERRREQUIREESM: CommonJS code…
Node warns when a single EventEmitter has more than 10 listeners for the same event (the default threshold). It is a warning, not an error — but it usually points at a genuine leak. Typical cause: registering a listener inside a repeated code path without…
Node runs JavaScript on one thread, but libuv keeps a thread pool for work the OS does not expose a good async API for. The pool defaults to 4 threads and is tuned via the UVTHREADPOOLSIZE environment variable (max 1024, and it must be set…
.pipe() does exactly one thing: move data while respecting backpressure. It does not propagate errors and does not clean up. When a stream in a.pipe(b).pipe(c) emits error: - The remaining streams are not destroyed → file descriptors, sockets, and buffers stay alive. - Without an…
Never load the whole file into RAM. Build a read → parse → batch → write pipeline and let backpressure pace the reader against the database. Why memory stays flat: writer.write only calls cb() after the database write completes. Until then the internal queue fills…
It solves carrying context across an async chain without threading an extra parameter through every function. Node has no thread-local storage; AsyncLocalStorage is the async-world equivalent: a store bound to a run scope that follows every await, callback, and promise chain spawned inside it. Deep…
There are two distinct causes, usually present together. 1. Closing before routing has caught up. When a pod is deleted, Kubernetes sends SIGTERM and removes the pod from Endpoints in parallel, not in sequence. kube-proxy and ingress controllers need time to update. If the process…
Since Node 15 the default for --unhandled-rejections changed from warn to throw: without an unhandledRejection handler, an unhandled rejection surfaces as an uncaught exception and kills the process. Previously it only printed a warning and the app kept running — which is why older code…
Express puts every app.use() and route handler into one stack in declaration order and walks it top-down per request. A middleware runs only if the request actually reaches its position. Two things stop the flow: - An earlier handler ends the response (res.send, res.json) without…
Three call forms, three flows: - next() — move to the next middleware/handler in the stack; the normal path. - next(err) — passing anything other than the string 'route' jumps straight to the error-handling middleware (the 4-argument kind), skipping all remaining regular middleware. - next('route')…
express.json() only parses bodies with Content-Type: application/json; express.urlencoded() only handles application/x-www-form-urlencoded. A form containing a file is sent as multipart/form-data — the body is split into parts separated by a boundary string, each part carrying its own headers. Neither parser understands that format, so req.body…
Express identifies an error handler purely by the function's arity (fn.length === 4). If you write (err, req, res) and drop next, Express treats it as a regular middleware where err is bound to req, and errors fall through to the default handler instead of…
Express 4 only wraps synchronous handler invocation in try/catch. An async function returns a Promise immediately, so a later failure surfaces as a rejected promise that Express never sees. Since Node 15, an unhandled rejection crashes the whole process by default (exit code 1), taking…
Once headers are flushed to the client, Node forbids changing them or starting a second response on the same request. The root cause is always responding twice to one request. Three patterns that trigger it: Fixes: - Always return res.json(...) on every early-exit branch. -…