🎯 Free Interview Preparation

NodeJS Interview Questions & Answers

Top NodeJS interview questions and answers for freshers, experienced developers and senior engineers.

NodeJS Interview Questions & Answers (2026)

Top NodeJS interview questions and answers for freshers, experienced developers and senior engineers.

Junior Question: What is NodeJS?

Level: Junior | Category: Fundamentals | Time: 7 mins

Expected Answer

NodeJS is a JavaScript runtime built on Google's V8 Engine that allows JavaScript to run outside the browser.

Key Characteristics

  • Single-threaded
  • Event-driven
  • Non-blocking I/O
  • Asynchronous programming model

Common Use Cases

  • REST APIs
  • Real-time applications
  • Microservices
  • Streaming services
  • Chat applications
Interviewer Note: 💡 Candidate should clearly explain that NodeJS is a runtime environment, not a framework.

Mid-Level Question: Explain the Event Loop in NodeJS

Level: Mid-Level | Category: Asynchronous & Event Loop | Time: 10 mins

Expected Answer

The Event Loop allows NodeJS to perform non-blocking operations even though JavaScript runs on a single thread.

Execution Flow

  1. Call Stack
  2. Node APIs / Libuv
  3. Callback Queue
  4. Event Loop

Example

Code Example

console.log('Start');

setTimeout(() => {
  console.log('Timer');
}, 0);

console.log('End');

Expected Answer

Output

Code Example

Start
End
Timer
Interviewer Note: 💡 Follow-Up Question: Why does Timer execute after End? The callback enters the Callback Queue and waits until the Call Stack becomes empty.

Mid-Level Practical: Predict the Output

Level: Mid-Level | Category: Asynchronous & Event Loop | Time: 10 mins

Code Example

console.log('A');

setTimeout(() => console.log('B'));

Promise.resolve()
  .then(() => console.log('C'));

console.log('D');

Expected Answer

Correct Output

Code Example

A
D
C
B

Expected Answer

Explanation

  • Promise callbacks enter Microtask Queue
  • Microtasks execute before Callback Queue
  • setTimeout callback executes last

Senior Question: Streams vs Buffers

Level: Senior | Category: Streams & Threads | Time: 12 mins

Expected Answer

Both are used for handling data, but work differently.

Streams

fs.createReadStream('large-file.txt');
  • Processes data in chunks
  • Low memory usage
  • Ideal for large files

Buffers

fs.readFile('large-file.txt');
  • Loads entire file into memory
  • Simpler API
  • Not ideal for huge files
Interviewer Note: 💡 Senior Candidate Candidate should discuss memory consumption, streaming performance and backpressure handling.

Senior Question: What are Worker Threads?

Level: Senior | Category: Streams & Threads | Time: 12 mins

Expected Answer

Worker Threads allow CPU-intensive tasks to run on separate threads, preventing the Event Loop from being blocked.

Code Example

const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js');

Expected Answer

Common Use Cases

  • Image processing
  • PDF generation
  • Data analysis
  • Encryption
Interviewer Note: 💡 When should Worker Threads NOT be used? For I/O operations because NodeJS already handles them asynchronously.

Practical Coding Round: Implement a Simple Rate Limiter

Level: Coding Challenge | Category: Streams & Threads | Time: 15 mins

Expected Answer

Restrict a user to a fixed number of requests.

Code Example

const requests = {};

function allowRequest(ip) {

  requests[ip] ??= 0;

  if (requests[ip] >= 5) {
    return false;
  }

  requests[ip]++;

  return true;
}

Expected Answer

Interview Discussion Points

  • Memory usage
  • Distributed systems
  • Redis implementation
  • Sliding window algorithm

Architect Level Question: How Would You Scale NodeJS to 10 Million Users?

Level: Architect | Category: Scaling & Architecture | Time: 15 mins

Expected Answer

Expected Topics

  • Load Balancer
  • Horizontal Scaling
  • Microservices
  • Redis Cache
  • Database Sharding
  • CDN
  • Message Queues
  • Monitoring
Interviewer Note: 💡 Red Flag: Candidate only talks about increasing server RAM. Strong Candidate: Discusses architecture, bottlenecks, caching strategy and distributed systems.

Junior Question #8: What is package.json and what is the difference between dependencies and devDependencies?

Level: Junior | Category: npm & Configuration | Time: 6 mins

Expected Answer

The package.json file is the manifest of a Node.js project. It holds metadata (name, version, main file) and manages project dependencies.

  • dependencies: Essential packages required to run the application in production (e.g., Express, Lodash).
  • devDependencies: Packages only needed during local development, testing, or building (e.g., Jest, ESLint, TypeScript compiler).

Code Example

{
  "name": "my-app",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "jest": "^29.5.0"
  }
}
Interviewer Note: 💡 Candidate should mention that npm install --production will skip devDependencies to save space in container deployments.

Junior Question #9: Explain the difference between exports and module.exports in CommonJS Modules.

Level: Junior | Category: Modules (CommonJS & ESM) | Time: 7 mins

Expected Answer

In CommonJS, module.exports is the actual object returned when requiring a module. exports is merely a shorthand variable pointing to the same memory reference as module.exports.

If you assign a new object or function to exports directly (e.g., exports = MyClass), you break the reference, and Node.js will still return the original module.exports (which remains empty).

Code Example

// Correct exports shorthand
exports.add = (a, b) => a + b;

// Correct default override
module.exports = (a, b) => a + b;
Interviewer Note: 💡 Ask: What happens if I write exports = { hello: 'world' }? Candidate should explain that this breaks the reference.

Junior Question #10: What is the purpose of package-lock.json and why should it be committed to version control?

Level: Junior | Category: npm & Configuration | Time: 6 mins

Expected Answer

The package-lock.json file locks down the exact version of all dependencies (and their nested dependencies) installed in the project. It records the precise dependency tree with hash checksums to ensure reproducible builds across machines.

It must be committed to git to prevent drift between local development, staging, and production environments.

Interviewer Note: 💡 Red Flag: Candidate suggests deleting package-lock.json to resolve merge conflicts.

Junior Question #11: How do you pass environment variables to a Node.js process and access them?

Level: Junior | Category: Process & Configuration | Time: 5 mins

Expected Answer

Environment variables are passed from the command line prefixing the execution script and are accessed inside the runtime via the global object process.env.

Code Example

// Command line execution:
// PORT=4000 node index.js

const port = process.env.PORT || 3000;
console.log(`Server listening on port ${port}`);
Interviewer Note: 💡 Ask about dotenv package usage for local development profiles.

Junior Question #12: What is the difference between setImmediate() and process.nextTick()?

Level: Junior | Category: Event Loop | Time: 8 mins

Expected Answer

Although they sound opposite:

  • process.nextTick() executes callbacks immediately after the current phase of the event loop completes, before the loop enters the next phase. It runs microtasks.
  • setImmediate() schedules execution on the next turn (tick) of the event loop during the check phase.

Code Example

process.nextTick(() => console.log('Tick'));
setImmediate(() => console.log('Immediate'));
// Output: 
// Tick
// Immediate
Interviewer Note: 💡 High-quality candidates warn that recursive process.nextTick calls can starve I/O execution.

Junior Question #13: How do you handle file path operations cross-platform in Node.js?

Level: Junior | Category: File System & Path | Time: 6 mins

Expected Answer

Instead of manual string concats using slashes, use the built-in path module methods like path.join() and path.resolve(). They automatically format paths with delimiters matching the runtime OS (Windows \ vs Unix /).

Code Example

const path = require('path');

// Safe cross-platform absolute path
const uploadDir = path.join(__dirname, 'uploads', 'images');
console.log(uploadDir);
Interviewer Note: 💡 Mention the difference between __dirname (current directory of the file) and process.cwd() (current working directory of Node process).

Junior Question #14: What is the EventEmitter class and how do you use it?

Level: Junior | Category: Events & Callbacks | Time: 7 mins

Expected Answer

The EventEmitter is a core class of the events module. It implements the Observer pattern, allowing one part of the code to emit named event markers that trigger registered listener callback functions.

Code Example

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

myEmitter.on('login', (user) => {
  console.log(`User logged in: ${user}`);
});

myEmitter.emit('login', 'Vijay');
Interviewer Note: 💡 Emphasize avoiding memory leaks by removing listeners via off() or removeListener().

Junior Question #15: What is the difference between fs.readFile and fs.readFileSync?

Level: Junior | Category: File System & Path | Time: 6 mins

Expected Answer

fs.readFile is asynchronous and non-blocking, delegating the reading task to the Libuv thread pool. fs.readFileSync is synchronous and blocking, stalling the main event loop thread until the entire file is fetched.

Interviewer Note: 💡 Highlight: readFileSync should only be used during application bootstrap configurations.

Junior Question #16: How do you create a basic HTTP server without frameworks?

Level: Junior | Category: HTTP & API Design | Time: 6 mins

Expected Answer

Use the built-in http module and call the createServer() factory method.

Code Example

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello NodeJS World');
});

server.listen(3000);
Interviewer Note: 💡 Junior candidates should understand req (IncomingMessage) and res (ServerResponse) params.

Junior Question #17: What is Express.js middleware?

Level: Junior | Category: Express.js | Time: 7 mins

Expected Answer

Middleware functions are functions that have access to the request object (req), response object (res), and the next middleware function in the application’s request-response cycle.

They can execute code, modify request/response objects, end request cycles, or call next() to pass control to subsequent handlers.

Code Example

const logger = (req, res, next) => {
  console.log(`${req.method} request received`);
  next(); // Mandatory to pass control
};
Interviewer Note: 💡 Red Flag: Forgetting to call next() resulting in hung requests.

Junior Question #18: What is the global scope in Node.js and how does it differ from browsers?

Level: Junior | Category: Node.js Fundamentals | Time: 5 mins

Expected Answer

In browsers, the top-level global scope is window. In Node.js, the global scope is global. Also, variables declared with var at the top level of a Node.js file are local to that module wrapper, not added to the global scope like browsers.

Interviewer Note: 💡 Good to mention globalThis keyword in modern ES2020+ standards.

Junior Question #19: What is the difference between npm and npx?

Level: Junior | Category: npm & Configuration | Time: 5 mins

Expected Answer

npm is the package manager used to install, update, and manage dependencies in a project. npx is a package runner tool shipped with npm. It allows executing CLI packages (e.g. create-react-app, prisma) directly from the registry without installing them globally.

Interviewer Note: 💡 Mention that npx downloads and executes the latest package in a temporary directory.

Junior Question #20: What are buffers in Node.js and why are they needed?

Level: Junior | Category: Streams & Buffers | Time: 6 mins

Expected Answer

A Buffer represents a fixed-size chunk of memory allocated outside the V8 heap. It is designed to handle raw binary data streams (such as files, TCP packets, images) which JavaScript originally had no native type for.

Code Example

const buf = Buffer.from('Hello');
console.log(buf); // Output raw bytes: 
console.log(buf.toString()); // Output: Hello
Interviewer Note: 💡 Juniors should know Buffer.alloc(size) allocates pre-cleared raw memory.

Junior Question #21: What is type coercion and how does Node.js developers avoid it?

Level: Junior | Category: Node.js Fundamentals | Time: 5 mins

Expected Answer

Type coercion is the automatic conversion of values from one data type to another (e.g., 5 + '5' = '55'). Node.js developers avoid coercion by using strict equality comparisons (=== and !==) instead of loose ones (== and !=).

Interviewer Note: 💡 Standard question, verify basic understanding of truthy/falsy.

Junior Question #22: Explain how routing works in Express.js

Level: Junior | Category: Express.js | Time: 6 mins

Expected Answer

Routing determines how an application responds to a client request to a particular endpoint (URI/path) and HTTP request method (GET, POST, etc.). Express supports route parameters and router groups.

Code Example

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

app.get('/users/:id', (req, res) => {
  res.send(`User ID: ${req.params.id}`);
});
Interviewer Note: 💡 Ask difference between route parameters (req.params) and query parameters (req.query).

Junior Question #23: How do you parse request body payloads in Express.js?

Level: Junior | Category: Express.js | Time: 6 mins

Expected Answer

Use built-in body parsing middlewares. Since Express 4.16+, body-parser is bundled within Express directly, enabling express.json() for JSON payloads and express.urlencoded() for form-submitted key-values.

Code Example

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

app.use(express.json()); // Parses JSON bodies
app.post('/data', (req, res) => {
  console.log(req.body); // holds JSON parsed content
  res.sendStatus(200);
});
Interviewer Note: 💡 Red Flag: Failing to register body parsing middleware and wondering why req.body is undefined.

Junior Question #24: What is the purpose of npm shrinkwrap?

Level: Junior | Category: npm & Configuration | Time: 6 mins

Expected Answer

npm shrinkwrap creates a locked manifest file (npm-shrinkwrap.json) that behaves exactly like package-lock.json, but is publishable to the public npm registry to enforce lock versions on consumer installs.

Interviewer Note: 💡 Standard locking explanation is sufficient.

Junior Question #25: What are core modules in Node.js and name a few?

Level: Junior | Category: Node.js Fundamentals | Time: 5 mins

Expected Answer

Core modules are modules pre-compiled into the Node.js binary. They don't require npm installs and are imported directly via require/import statements.

Examples: fs, path, http, os, crypto, events, util.

Interviewer Note: 💡 Junior check: Can they enumerate basic built-ins?

Junior Question #26: What is REPL in Node.js?

Level: Junior | Category: Node.js Fundamentals | Time: 4 mins

Expected Answer

REPL stands for **Read-Eval-Print-Loop**. It is the interactive shell interface launched when running the node command alone, allowing immediate execution of JS commands for testing.

Interviewer Note: 💡 Standard definition.

Junior Question #27: Explain how query strings are read in Express.js

Level: Junior | Category: Express.js | Time: 5 mins

Expected Answer

Query parameters following the ? symbol in an API url are parsed by Express automatically and populated inside the req.query object.

Code Example

// GET /search?q=nodejs&limit=10
app.get('/search', (req, res) => {
  const query = req.query.q;
  const limit = req.query.limit;
  res.send(`Search for ${query}, limit ${limit}`);
});
Interviewer Note: 💡 Make sure they understand difference between params and query.

Mid-Level Question #28: Explain backpressure in Node.js streams and how to handle it.

Level: Mid-Level | Category: Streams & Buffers | Time: 10 mins

Expected Answer

Backpressure occurs when the data writing buffer speed is slower than the data reading buffer speed in streams (e.g. fast disk read piped to a slow HTTP client). This leads to memory accumulation and eventually OOM crashes.

To handle it, stream consumers notify publishers to pause reading by returning false on write(). The stream then fires the drain event once the buffer clears, indicating it is safe to resume reading.

Code Example

const fs = require('fs');
const readStream = fs.createReadStream('large.txt');
const writeStream = fs.createWriteStream('copy.txt');

readStream.on('data', (chunk) => {
  const canContinue = writeStream.write(chunk);
  if (!canContinue) {
    readStream.pause(); // Pause reading to handle backpressure
  }
});

writeStream.on('drain', () => {
  readStream.resume(); // Resume reading once buffer is flushed
});
Interviewer Note: 💡 Candidate should mention that .pipe() handles backpressure out of the box.

Mid-Level Question #29: What is child_process.fork() and how does IPC (Inter-Process Communication) work?

Level: Mid-Level | Category: Performance & Concurrency | Time: 9 mins

Expected Answer

child_process.fork() is a special helper of spawn to create a new Node.js child process executing a specified JS module. Unlike standard processes, fork creates an IPC channel allowing bidirectional message passage between the parent and child via send() and on('message').

Code Example

// parent.js
const { fork } = require('child_process');
const child = fork('child.js');

child.send({ hello: 'child' });
child.on('message', (msg) => console.log('Parent received:', msg));

// child.js
process.on('message', (msg) => {
  console.log('Child received:', msg);
  process.send({ hello: 'parent' });
});
Interviewer Note: 💡 Ask: How does fork compare to spawn? Fork runs a new Node.js engine, whereas spawn executes any system CLI binary.

Mid-Level Question #30: Compare spawn(), exec(), execFile(), and fork() methods in child_process.

Level: Mid-Level | Category: Performance & Concurrency | Time: 10 mins

Expected Answer

These helpers spawn child processes with specific configurations:

  • spawn(): Stream-based execution of CLI commands. Best for returning large amounts of data dynamically without blocking memory.
  • exec(): Spawns a shell and buffers the output (default limit: 200KB). Best for running small scripts and receiving immediate output.
  • execFile(): Runs a file binary directly without spawning a shell, saving memory and improving execution speed.
  • fork(): Spawns a new Node.js process and creates an IPC communications channel.
Interviewer Note: 💡 Red Flag: Spawning exec() on user-inputted strings, introducing shell injection vulnerabilities.

Mid-Level Question #31: What is libuv, what is its default thread pool size, and how do you change it?

Level: Mid-Level | Category: Event Loop | Time: 9 mins

Expected Answer

libuv is the multi-platform C library supporting asynchronous I/O operations in Node.js. It manages the Event Loop and a Worker Thread Pool for task delegations that are not natively asynchronous at the OS level (like file system access, crypto hashing, DNS resolution).

The default size is 4 threads. It can be modified on startup by setting the environment variable UV_THREADPOOL_SIZE (up to a limit of 1024).

Interviewer Note: 💡 Ask: Which core modules utilize the thread pool? (fs, crypto, zlib, dns.lookup).

Mid-Level Question #32: What is JWT (JSON Web Tokens) and how do you implement token authentication in Node.js?

Level: Mid-Level | Category: Security | Time: 10 mins

Expected Answer

JWT is a stateless credential format consisting of three base64url strings: Header, Payload, and Signature. It is passed via authorization request headers to authenticate users without storing session states in database tables.

Code Example

const jwt = require('jsonwebtoken');

// Generate Token
const token = jwt.sign({ userId: 123 }, 'secret_key', { expiresIn: '1h' });

// Verify Token Middleware
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('Access Denied');
  
  try {
    const decoded = jwt.verify(token, 'secret_key');
    req.user = decoded;
    next();
  } catch (err) {
    res.status(403).send('Invalid Token');
  }
};
Interviewer Note: 💡 JWTs are stateless; revoking them prior to expiration requires blacklist checks or db tokens validation.

Mid-Level Question #33: How does session-based authentication differ from token-based authentication?

Level: Mid-Level | Category: Security | Time: 9 mins

Expected Answer

Differences are centered around state management:

  • Session-based: Stateful. The server creates a session entry in a database/memory cache (Redis) and returns a unique session ID stored in client cookies. Subsequent queries validate the session ID against the server session repository.
  • Token-based: Stateless. The server issues a signed JWT containing client data. The client stores it locally (LocalStorage or HttpOnly Cookies) and attaches it to request headers. The server verifies signature validity without querying cache databases.
Interviewer Note: 💡 Senior candidate should point out that JWTs cannot easily be invalidated on-demand.

Mid-Level Question #34: How do you hash passwords securely in Node.js?

Level: Mid-Level | Category: Security | Time: 8 mins

Expected Answer

Do not use simple hash algorithms like MD5 or SHA256 alone because they are vulnerable to dictionary and rainbow table lookups. Instead, use slow, adaptive hashing libraries like bcrypt or Node.js native crypto.scrypt(), applying unique salt strings.

Code Example

const bcrypt = require('bcrypt');

// Hash password
const hashPassword = async (pwd) => {
  const salt = await bcrypt.genSalt(10);
  return await bcrypt.hash(pwd, salt);
};

// Compare password
const verifyPassword = async (pwd, hash) => {
  return await bcrypt.compare(pwd, hash);
};
Interviewer Note: 💡 Check: Does the candidate understand why salting passwords is required?

Mid-Level Question #35: How do you implement rate limiting in an Express application?

Level: Mid-Level | Category: Express.js | Time: 8 mins

Expected Answer

Rate limiting restricts the number of requests a client can make in a specified window of time, preventing DDoS and brute-force scenarios. Locally, you can use packages like express-rate-limit, but distributed architectures require storing hits in a central Redis cache database.

Code Example

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

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // max 100 requests per IP
  message: 'Too many requests, please try again later.'
});

app.use('/api/', apiLimiter);
Interviewer Note: 💡 Ask: What headers does a rate limiter return? (X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After).

Mid-Level Question #36: What is Helmet middleware and how does it secure Express headers?

Level: Mid-Level | Category: Security | Time: 7 mins

Expected Answer

helmet is an Express middleware collection that sets various secure HTTP response headers to defend against clickjacking, sniff attacks, XSS, and server details identification.

It hides headers like X-Powered-By: Express and enforces security constraints like Strict-Transport-Security, X-Content-Type-Options: nosniff, and Content-Security-Policy.

Interviewer Note: 💡 Ask: Why should we hide the X-Powered-By header? (To prevent attackers from profiling our framework versions).

Mid-Level Question #37: Compare WebStreams and Node.js standard Streams.

Level: Mid-Level | Category: Streams & Buffers | Time: 9 mins

Expected Answer

Node.js standard streams (e.g. Readable, Writable) are proprietary to the Node.js runtime and emit events like data, end, and error. WebStreams are standard web API stream specifications (e.g. ReadableStream, WritableStream) supported inside browsers, Cloudflare Workers, and modern Node.js versions, allowing isomorphic streams logic.

Interviewer Note: 💡 Modern node APIs (like fetch) return WebStreams by default.

Mid-Level Question #38: What is AbortController and how is it used in Node.js?

Level: Mid-Level | Category: Async Programming | Time: 8 mins

Expected Answer

AbortController is a standard controller object that communicates termination requests to async tasks (like fetch requests, file reads, or timeouts) using an AbortSignal.

Code Example

const controller = new AbortController();
const { signal } = controller;

// Cancel fetch query after 500ms
setTimeout(() => controller.abort(), 500);

try {
  const res = await fetch('https://api.example.com/data', { signal });
  const data = await res.json();
} catch (err) {
  if (err.name === 'AbortError') console.log('Request cancelled!');
}
Interviewer Note: 💡 Mention that AbortController was standardized in Node.js 15+.

Mid-Level Question #39: How do you test Node.js controllers using Jest and Supertest?

Level: Mid-Level | Category: Testing | Time: 10 mins

Expected Answer

Supertest allows testing HTTP server endpoints without binding the Express application to network ports. Jest manages the test framework assertions, setup/teardown processes, and execution hooks.

Code Example

const request = require('supertest');
const app = require('./app'); // Express app object

describe('GET /api/users', () => {
  it('should return list of users', async () => {
    const res = await request(app)
      .get('/api/users')
      .expect('Content-Type', /json/)
      .expect(200);
    
    expect(res.body).toBeInstanceOf(Array);
  });
});
Interviewer Note: 💡 Emphasize not calling app.listen() inside app.js directly, export app and call listen inside server.js to allow testing isolation.

Mid-Level Question #40: Explain Node.js 20+ built-in Test Runner API.

Level: Mid-Level | Category: Testing | Time: 8 mins

Expected Answer

Node.js 20 introduces a stable built-in test runner via the node:test module, removing external test dependencies (like Jest or Mocha) for lightweight test scripts.

Code Example

const test = require('node:test');
const assert = require('node:assert');

test('synchronous addition test', (t) => {
  assert.strictEqual(1 + 1, 2);
});
Interviewer Note: 💡 Ask about test runner coverage and execution speed benefits.

Mid-Level Question #41: What is the difference between process.exit(0) and process.exit(1)?

Level: Mid-Level | Category: Process & Configuration | Time: 5 mins

Expected Answer

Both exit code numbers tell the parent OS process status when Node exits:

  • 0: Success/Normal termination. The script executed completely with no runtime errors.
  • 1: Failure/Abnormal exit. Tells orchestrators (like Kubernetes, PM2, or Docker) that an error occurred, prompting restart configurations.
Interviewer Note: 💡 Good candidate notes that process.exit should rarely be called inside API controllers.

Mid-Level Question #42: How does Node.js resolve modules under node_modules (Module Resolution Algorithm)?

Level: Mid-Level | Category: Modules (CommonJS & ESM) | Time: 9 mins

Expected Answer

Node.js starts resolving from the current file folder looking for a node_modules folder. If not found, it climbs up parent directory folders iteratively to the root directory. Inside the module folder, it reads the package.json exports, main entry, or defaults to index.js.

Interviewer Note: 💡 Ask: How does node behave when a file import lacks extension? It resolves .js, .json, and .node in order.

Mid-Level Question #43: How does the util.promisify method work?

Level: Mid-Level | Category: Async Programming | Time: 7 mins

Expected Answer

util.promisify transforms standard node callback-based functions (expecting error-first callback params) into clean Promise-returning utilities compatible with async/await patterns.

Code Example

const { promisify } = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);

try {
  const content = await readFileAsync('file.txt', 'utf8');
  console.log(content);
} catch (err) {
  console.error(err);
}
Interviewer Note: 💡 Ask: What are callback signature standards for promisify? (Error first, data second).

Mid-Level Question #44: What is the purpose of process.on('uncaughtException')?

Level: Mid-Level | Category: Process & Configuration | Time: 8 mins

Expected Answer

It listens to unhandled synchronous errors that bubble up to the global execution scope. This prevents standard crashes from dropping active requests, although it is best practice to log the error and terminate the process gracefully to avoid memory corruption states.

Code Example

process.on('uncaughtException', (err) => {
  console.error('Fatal Uncaught Exception:', err);
  // Gracefully terminate application after logging
  process.exit(1);
});
Interviewer Note: 💡 Red Flag: Listening to uncaughtException, logging it, and keeping the process running, which leads to socket leaks.

Mid-Level Question #45: What is process.on('unhandledRejection')?

Level: Mid-Level | Category: Process & Configuration | Time: 7 mins

Expected Answer

It captures promise rejections that lack corresponding catch() handlers. Starting in modern Node.js versions, unhandled rejections will exit the process with status code 1 if not handled.

Interviewer Note: 💡 Emphasize registering catch blocks on all promise pipelines.

Mid-Level Question #46: How do you connect WebSockets using the ws package in Node.js?

Level: Mid-Level | Category: Express.js | Time: 8 mins

Expected Answer

WebSockets enable persistent, full-duplex TCP communication channels over port connections. In Node.js, the ws package sets up lightweight WebSocket servers.

Code Example

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws) => {
  ws.on('message', (message) => {
    console.log(`Received: ${message}`);
    ws.send('Acknowledged');
  });
});
Interviewer Note: 💡 Contrast WebSocket with standard polling or SSE (Server-Sent Events).

Mid-Level Question #47: What is Joi / Zod and why is request validation essential?

Level: Mid-Level | Category: Express.js | Time: 8 mins

Expected Answer

Validation libraries like Zod or Joi enforce strict type schemas on client input (req.body, req.query, req.params) before route controllers run, preventing SQL injection, schema corruption, or invalid arguments from crashing the app.

Code Example

const { z } = require('zod');

const userSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18)
});

app.post('/register', (req, res) => {
  const result = userSchema.safeParse(req.body);
  if (!result.success) return res.status(400).json(result.error);
  res.send('Valid payload');
});
Interviewer Note: 💡 Zod supports TypeScript type inference out of the box.

Mid-Level Question #48: What is the difference between path.join() and path.resolve()?

Level: Mid-Level | Category: File System & Path | Time: 7 mins

Expected Answer

Path processing differences:

  • path.join(): Joins all path segments together using OS path delimiters, returning a normalized relative path string.
  • path.resolve(): Resolves path segments into an absolute path. It processes inputs from right to left, prefixing the current working directory (cwd) if a absolute path is not resolved.
Interviewer Note: 💡 Ask: If path.resolve('/a', 'b') runs, what is the output? (/a/b absolute path).

Mid-Level Question #49: How do you run shell commands using child_process.exec()?

Level: Mid-Level | Category: Performance & Concurrency | Time: 7 mins

Expected Answer

exec runs command line inputs inside a system shell and passes the buffered outputs to a callback function.

Code Example

const { exec } = require('child_process');

exec('ls -lh', (error, stdout, stderr) => {
  if (error) return console.error(`Error: ${error.message}`);
  console.log(`Output: ${stdout}`);
});
Interviewer Note: 💡 Red flag check: exec holds the output in memory buffer (default 200KB). Large output will crash the application.

Mid-Level Question #50: What are the common HTTP response codes used in Node.js REST APIs?

Level: Mid-Level | Category: HTTP & API Design | Time: 6 mins

Expected Answer

Standard REST codes include:

  • 200: OK (Success)
  • 201: Created (Success write)
  • 400: Bad Request (Validation failed)
  • 401: Unauthorized (No auth credentials)
  • 403: Forbidden (Insufficient permission credentials)
  • 404: Not Found
  • 500: Internal Server Error
Interviewer Note: 💡 Ask difference between 401 and 403 (unauthenticated vs unauthorized).

Senior Question #51: Explain V8 memory spaces (New, Old, Large Objects) and how Garbage Collection works.

Level: Senior | Category: Memory & Concurrency | Time: 12 mins

Expected Answer

V8 divides heap memory into spaces for optimized GC cycles:

  • New Space (Nursery): Small space (1-8MB) where new allocations occur. It uses a fast **Scavenge (Copy)** collector. Surviving objects are promoted to Old Space.
  • Old Space: Holds long-lived objects. Divided into Old Pointer Space and Old Data Space. It uses **Mark-Sweep-Compact** algorithms.
  • Large Object Space: Holds objects exceeding size allocations of other spaces, avoiding copy overhead.

V8 GC runs incrementally and concurrently to minimize pause times on the main event loop thread.

Interviewer Note: 💡 Ask: How does node flag GC cycles? (Use node --expose-gc flag to trigger gc programmatically).

Senior Question #52: How do you detect, profile, and debug memory leaks in Node.js?

Level: Senior | Category: Memory & Concurrency | Time: 12 mins

Expected Answer

Memory leak detection and debugging flow:

  1. Detection: Monitor memory usage (RSS/heapUsed) growing consistently over time in APMs (Datadog, Grafana) or using process.memoryUsage().
  2. Snapshot Generation: Run node with --inspect, connect Chrome DevTools, and take Heap Snapshots at different intervals (before and after load tests).
  3. Comparison: Compare snapshots to find growing allocations (often unreleased event listeners, global arrays, or closures).

Code Example

// Programmatic snapshot generation
const v8 = require('v8');
const fs = require('fs');

const snapshotStream = v8.getHeapSnapshot();
const fileName = `${Date.now()}.heapsnapshot`;
const fileStream = fs.createWriteStream(fileName);
snapshotStream.pipe(fileStream);
Interviewer Note: 💡 Seniors should reference tools like clinic.js (clinic doctor / clinic heapprofiler).

Senior Question #53: How do you perform CPU profiling on a Node.js process?

Level: Senior | Category: Performance & Diagnostics | Time: 11 mins

Expected Answer

Use the built-in V8 profiler to diagnose event loop delays:

  1. Run Node with the built-in tick profiler: node --prof app.js.
  2. Run load tests to simulate CPU pressure.
  3. Process the generated log file: node --prof-process isolate-0xXXXX-v8.log > processed.txt.
  4. Analyze the output to locate hot paths (blocking regex, crypto, parsing).
Interviewer Note: 💡 Good to mention flamegraph tools to visualize call stack frequencies.

Senior Question #54: Compare Worker Threads and Child Processes in Node.js.

Level: Senior | Category: Performance & Concurrency | Time: 12 mins

Expected Answer

Differences are centered around memory sharing models:

  • Child Processes: Each process runs on its own OS process, possessing its own V8 instance and isolated memory. Communication occurs through serialized IPC messaging. Best for external shell executions or long running tasks.
  • Worker Threads: Run inside the same OS process on separate threads. They share memory through SharedArrayBuffer and avoid expensive serialization overheads. Best for CPU-intensive JavaScript operations.

Code Example

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.on('message', (msg) => console.log('Received:', msg));
} else {
  parentPort.postMessage('Done');
}
Interviewer Note: 💡 Worker Threads do not make Node.js multi-threaded for standard I/O; they target CPU loads.

Senior Question #55: How do you implement Redis distributed caching in Node.js?

Level: Senior | Category: Scaling & Infrastructure | Time: 12 mins

Expected Answer

Implement a Cache-Aside pattern. Look up key data in Redis cache; on miss, query database, save results in Redis with a TTL (Time To Live), and return payload.

Code Example

const redis = require('redis');
const client = redis.createClient({ url: 'redis://localhost:6379' });

const getUserCached = async (userId) => {
  const cached = await client.get(`user:${userId}`);
  if (cached) return JSON.parse(cached);
  
  const user = await db.queryUser(userId);
  await client.setEx(`user:${userId}`, 3600, JSON.stringify(user)); // 1h TTL
  return user;
};
Interviewer Note: 💡 Ask about cache stampede (dogpiling) and how to handle lock synchronizations.

Senior Question #56: Explain the Circuit Breaker pattern and how to implement it in Node.js.

Level: Senior | Category: Scaling & Infrastructure | Time: 12 mins

Expected Answer

A Circuit Breaker wraps third-party network calls. If failures exceed a threshold (e.g. 50% errors), the breaker trips (opens) and immediately returns error responses instead of routing queries that pile up sockets on hanging downstreams. It moves to a **Half-Open** probe state after a cooldown to test if services recovered.

Code Example

const opossum = require('opossum');

const asyncFunction = async (id) => {
  const res = await fetch(`https://partner-api/users/${id}`);
  return res.json();
};

const options = {
  timeout: 3000, // 3s timeout
  errorThresholdPercentage: 50, // open if 50% fail
  resetTimeout: 30000 // try reset after 30s
};

const breaker = new opossum(asyncFunction, options);
breaker.fallback(() => ({ error: 'Service Unavailable' }));

const data = await breaker.fire(123);
Interviewer Note: 💡 Essential for microservice resiliency design questions.

Senior Question #57: How do you handle database connection pooling in high-concurrency Node.js apps?

Level: Senior | Category: HTTP & API Design | Time: 10 mins

Expected Answer

Opening a TCP connection for every query introduces latency and CPU spikes. Connection pooling maintains a pre-allocated pool of active database connections. Node.js processes lease active connections from the pool, execute SQL queries, and immediately return them to the pool.

Interviewer Note: 💡 Red Flag: Opening new client connections in request handlers and forgetting to call end().

Senior Question #58: Explain Event Sourcing pattern and how to design it.

Level: Senior | Category: Design Patterns | Time: 11 mins

Expected Answer

Instead of updating database entity row values in place, Event Sourcing records every state modification as an immutable, sequential event stream (e.g. OrderCreated, ItemAdded, OrderPaid). The current state of an entity is derived by replaying (reducing) the events chronologically.

Interviewer Note: 💡 Candidate should discuss CQRS (Command Query Responsibility Segregation) integration.

Senior Question #59: Explain the Node.js Permission Model introduced in modern releases.

Level: Senior | Category: Security | Time: 10 mins

Expected Answer

Introduced as an experimental feature in Node.js 20, the Permission Model allows restricting runtime access to resources such as the file system, network, and child processes using security CLI flags.

  • --allow-fs-read/--allow-fs-write: Limit folder/file access.
  • --allow-child-process: Disable/restrict spawning child processes.
Interviewer Note: 💡 Showcases that the candidate stays current with Node.js release cycles.

Senior Question #60: How do you implement structured logging in a production Node.js application?

Level: Senior | Category: Performance & Diagnostics | Time: 9 mins

Expected Answer

In production, console.log should be avoided because it blocks synchronously when outputting to standard out. Use structured loggers like pino or winston that output log events as JSON formats, enabling quick indexing in Logstash/Elasticsearch aggregators.

Code Example

const pino = require('pino');
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  mixin() { return { correlationId: 'tx-123' }; }
});

logger.info({ userId: 456 }, 'User successfully authenticated');
Interviewer Note: 💡 Ask: Why JSON formats? (To parse structured metadata values cleanly in logging pipelines).

Senior Question #61: Explain SQL Injection (SQLi) and how to prevent it in raw queries.

Level: Senior | Category: Security | Time: 9 mins

Expected Answer

SQLi occurs when untrusted user input is directly concatenated into SQL strings, allowing malicious payloads to alter the query command structure. Prevent it by using **Prepared Statements** (parameterized queries) or modern ORMs, separating query structure from variables.

Code Example

// VULNERABLE
const query = `SELECT * FROM users WHERE email = '${email}'`;

// SECURE
const query = 'SELECT * FROM users WHERE email = ?';
const [rows] = await db.execute(query, [email]);
Interviewer Note: 💡 Verify they understand parameterized placeholders are treated strictly as literals, not executable commands.

Senior Question #62: How do you prevent XSS and CSRF attacks in Express applications?

Level: Senior | Category: Security | Time: 11 mins

Expected Answer

Prevention strategies:

  • XSS (Cross-Site Scripting): Sanitize user HTML inputs using libraries like dompurify. Configure strict Content-Security-Policy (CSP) headers using Helmet.
  • CSRF (Cross-Site Request Forgery): Store JWT/session tokens inside secure, HttpOnly cookies configured with SameSite=Strict attribute. Require CSRF tokens on state-changing requests (POST/PUT/DELETE).
Interviewer Note: 💡 Ask: What does HttpOnly cookie do? (Prevents client-side scripts from reading the token).

Senior Question #63: What is the Event Loop block detector and how do you prevent loop starvation?

Level: Senior | Category: Event Loop | Time: 11 mins

Expected Answer

Event loop block/starvation occurs when synchronous operations (long JSON.parse, loops, crypto) hog the main execution thread, preventing microtasks and callbacks from running. You can detect blocking behavior using tools like `blocked-at` or node diagnostics hooks.

Code Example

// Split heavy work with setImmediate
function processChunks(data) {
  if (data.length === 0) return;
  doSmallSegment(data.shift());
  
  // Defer next segment to prevent loop blocking
  setImmediate(() => processChunks(data));
}
Interviewer Note: 💡 Seniors must prioritize breaking up synchronous CPU tasks.

Senior Question #64: How does DNS lookup resolution work in Node.js and its connection pool?

Level: Senior | Category: Event Loop | Time: 10 mins

Expected Answer

Node.js core methods like dns.lookup() call the system-level C API getaddrinfo(), which is a blocking operation. Node handles this by running the lookup inside the Libuv thread pool. If the thread pool is full, DNS lookups stall, adding latency to HTTP requests.

To avoid this dependency, use dns.resolve() instead, which runs asynchronously over network sockets, bypassing the libuv threads.

Interviewer Note: 💡 Excellent optimization topic for high-concurrency external API requests.

Senior Question #65: How do you write a custom Transform stream in Node.js?

Level: Senior | Category: Streams & Buffers | Time: 11 mins

Expected Answer

A Transform stream is a type of Duplex stream where the output is computed from the input. You write it by extending the Transform class from stream module and overriding the _transform() method.

Code Example

const { Transform } = require('stream');

class UpperCaseTransform extends Transform {
  _transform(chunk, encoding, callback) {
    const upperCased = chunk.toString().toUpperCase();
    this.push(upperCased);
    callback(); // Indicate processing is complete
  }
}

process.stdin.pipe(new UpperCaseTransform()).pipe(process.stdout);
Interviewer Note: 💡 Candidates should explain why chunk conversion chunk.toString() requires encoding safety.

Senior Question #66: What are best practices for structuring a production-ready Node.js monorepo?

Level: Senior | Category: Design Patterns | Time: 11 mins

Expected Answer

Monorepo best practices:

  • Use package managers with built-in workspace support (npm workspaces, pnpm, yarn).
  • Separate core shared business logic (e.g. database schemas, authentication middleware) into isolated local packages referenced by apps.
  • Utilize monorepo orchestration tools (turborepo, nx) to cache builds, lint, and run tests globally.
Interviewer Note: 💡 High-quality candidates focus on build caching and module isolation.

Senior Question #67: How do you configure PM2 for zero-downtime hot reloads in cluster mode?

Level: Senior | Category: Performance & Diagnostics | Time: 10 mins

Expected Answer

PM2 cluster mode creates child instances to scale Node across multiple CPU cores. For zero-downtime reloads, configure PM2 with reload instead of restart, and configure `listen_timeout` to wait for app boot verification.

Code Example

// ecosystem.config.js
module.exports = {
  apps: [{
    name: "app",
    script: "./app.js",
    instances: "max", // Scale across all available CPU cores
    exec_mode: "cluster",
    listen_timeout: 3000,
    wait_active: true
  }]
};
Interviewer Note: 💡 Candidate should explain that reload spawns new processes before killing old ones.

Senior Question #68: How do you build multi-stage Docker builds for Node.js applications?

Level: Senior | Category: Scaling & Infrastructure | Time: 11 mins

Expected Answer

Multi-stage Docker builds separate build tool chains (webpack, ts-compilers) from the runtime runtime image to minimize target container footprint and security attack surface.

Code Example

# Build Stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production Stage
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=build /app/dist ./dist
CMD ["node", "dist/main.js"]
Interviewer Note: 💡 Alpine base tags reduce final sizes from 1GB to ~100MB.

Senior Question #69: Explain how prototype pollution works in Node.js and how to defend against it.

Level: Senior | Category: Security | Time: 10 mins

Expected Answer

Prototype pollution is a vulnerability where attackers inject properties into the JavaScript base object prototype (Object.prototype) using keys like __proto__ or constructor.prototype, altering behavior in all created objects.

Defense: Use Object.create(null) for dictionary objects, schema validate user payload properties, or use Object.freeze(Object.prototype) on startup.

Interviewer Note: 💡 Classic JavaScript/Node.js security round question.

Senior Question #70: How do you implement OAuth 2.0 PKCE flow in a Node.js API?

Level: Senior | Category: Security | Time: 11 mins

Expected Answer

PKCE (Proof Key for Code Exchange) secures client authentication. The client generates a high-entropy cryptographically secure string called code_verifier, hashes it to create `code_challenge`, and sends the challenge on initial authorization. On redirect token retrieval, the client sends the original verifier, which Node validates by re-hashing.

Interviewer Note: 💡 Essential flow for mobile and single page app authentication integrations.

Architect Question #71: Design a distributed rate limiter that scales across a cluster of 50 Node.js API servers.

Level: Architect | Category: Scaling & Infrastructure | Time: 15 mins

Expected Answer

A local memory limiter fails in clusters because traffic is spread randomly. A distributed rate limiter leverages a central Redis cache using a **sliding window counter** pattern. To minimize network latency, we implement the rate checking logic inside a **Redis Lua Script**, ensuring atomic executions in a single network round-trip.

Code Example

// Lua script for sliding window rate limiting
const rateLimitLua = `
  local key = KEYS[1]
  local now = tonumber(ARGV[1])
  local window = tonumber(ARGV[2])
  local limit = tonumber(ARGV[3])
  local clearBefore = now - window
  
  redis.call('zremrangebyscore', key, 0, clearBefore)
  local amount = redis.call('zcard', key)
  if amount < limit then
    redis.call('zadd', key, now, now)
    redis.call('expire', key, window)
    return 1
  else
    return 0
  end
`;
Interviewer Note: 💡 Candidate should discuss Redis clustering, failover, and local-cache fallbacks in case Redis goes offline.

Architect Question #72: Design a system architecture to handle 100,000 concurrent WebSocket connections.

Level: Architect | Category: Scaling & Infrastructure | Time: 15 mins

Expected Answer

High-concurrency WebSocket systems require a horizontally scaled layer of Node.js instances backed by a Redis Pub/Sub backplane. Key components include:

  1. Load Balancing: An Nginx or AWS ALB layer configured with IP Hash (sticky sessions) to upgrade TCP socket connections.
  2. Horizontal Scale: Scaling Node instances across clusters, keeping connections alive by modifying OS limits (nofile setting to 200,000+ files).
  3. Pub/Sub Sync: When a user on Server A messages a user on Server B, the message publishes to a Redis channel, prompting Server B to broadcast to the targeted client.
Interviewer Note: 💡 Highlight the difference in memory footprints per connection: tuning socket timeout parameters to prune inactive sockets.

Architect Question #73: How do you design a secure microservices authentication architecture using API Gateways?

Level: Architect | Category: Scaling & Infrastructure | Time: 15 mins

Expected Answer

Authentication should be centralized at the API Gateway layer (e.g. Kong, Apigee, or a custom Express gateway). The gateway verifies incoming JWTs or OAuth tokens. Upon successful validation, it injects custom header attributes (such as x-user-id, x-user-roles) and routes the request to target downstream services. Downstream microservices remain stateless and trust requests that match internal network authorization signatures.

Interviewer Note: 💡 Architect candidates should point out that this pattern isolates microservices from auth details.

Architect Question #74: Design a distributed background job queue system using BullMQ and Redis.

Level: Architect | Category: Design Patterns | Time: 15 mins

Expected Answer

Background processors avoid blocking client-facing API responses. BullMQ implements a producer-consumer model using Redis streams and sorting sets to support job retries, delays, prioritization, concurrency parameters, and concurrency limits.

Code Example

const { Queue, Worker } = require('bullmq');

// Producer side (in Express API controller)
const emailQueue = new Queue('emails', { connection: redisConfig });
await emailQueue.add('welcomeEmail', { email: 'vijay@example.com' });

// Consumer side (in Worker process node)
const worker = new Worker('emails', async (job) => {
  await sendEmail(job.data.email);
}, { connection: redisConfig, concurrency: 10 });
Interviewer Note: 💡 Discuss how to handle task failures and prevent infinite loops with dead letter queues.

Architect Question #75: How would you implement database sharding and dynamic query routing in Node.js?

Level: Architect | Category: Design Patterns | Time: 14 mins

Expected Answer

Database sharding partitions tables horizontally across separate database engines. Dynamic routing calculates shard destinations based on a partition key (e.g. userId % totalShards). Node handles routing dynamically by instantiating database pools for each shard and routing queries inside middleware/repositories using the computed shard key.

Interviewer Note: 💡 Focus on how schema modifications and distributed joins become complex when databases are sharded.

Architect Question #76: Design a telemetry, monitoring, and observability pipeline for a Node.js ecosystem.

Level: Architect | Category: Performance & Diagnostics | Time: 15 mins

Expected Answer

A robust observability stack relies on three pillars: Metrics, Traces, and Logs.

  1. Metrics: Expose Prometheus metrics (/metrics endpoint) capturing Node process states (event loop delay, heap size, active handles) using prom-client.
  2. Traces: Instrument applications with OpenTelemetry (OTel) SDK. Inject span contexts on external queries to enable distributed trace charts (Jaeger, Zipkin).
  3. Logs: Stream JSON structured logs to Elasticsearch/Logstash aggregator layers.
Interviewer Note: 💡 Ask: How does event loop delay monitoring help? (It measures if synchronous operations are blocking the loop).

Architect Question #77: Design a zero-downtime deployment strategy for Node.js APIs in Kubernetes.

Level: Architect | Category: Scaling & Infrastructure | Time: 15 mins

Expected Answer

Zero-downtime relies on Kubernetes **Rolling Updates** combined with proper Node.js shutdown hook listeners. Key configurations include:

  • Readiness Probe: Monitors if Node is booted and databases are connected before directing traffic.
  • Liveness Probe: Monitors if the process crashed or hung.
  • SIGTERM Handler: Node catches SIGTERM, immediately closes HTTP servers (stopping new connections), waits for active requests to finish (graceful shutdown window), and exits.

Code Example

const server = app.listen(3000);

process.on('SIGTERM', () => {
  console.log('SIGTERM received. Starting graceful shutdown...');
  server.close(() => {
    console.log('HTTP server closed. Exiting process.');
    db.closeConnection().then(() => process.exit(0));
  });
});
Interviewer Note: 💡 Crucial: Candidate should know that process.exit(0) must not run immediately upon SIGTERM to prevent dropping active TCP sockets.

Architect Question #78: Explain clean architecture folder structure and module isolation in Enterprise Node.js.

Level: Architect | Category: Design Patterns | Time: 14 mins

Expected Answer

Clean Architecture enforces separation of concerns by placing business logic at the core, independent of external frameworks (Express, NestJS, databases, ORMs). The folder layers are:

  1. Domain (Entities): Central business models and logic (pure JS/TS).
  2. Use Cases (Application): Orchestrates domain models. Declares interface abstractions for external integrations.
  3. Interface Adapters (Controllers, Repositories): Converts HTTP or database inputs into domain models.
  4. Frameworks & Drivers: External frameworks (Express router files, Sequelize/Prisma config pools).
Interviewer Note: 💡 Red Flag: Importing database drivers or ORM files inside domain entities.

Architect Question #79: How do you handle distributed transactions and eventual consistency in Node.js microservices?

Level: Architect | Category: Scaling & Infrastructure | Time: 15 mins

Expected Answer

Two-Phase Commit (2PC) blocks databases and fails at scale. We solve this by implementing the **Saga Pattern** (orchestrated or choreographed) or **Outbox Pattern**.

  • Saga: Each service executes a local transaction and publishes an event. If a downstream service fails, compensating transactions are triggered backwards to roll back previous steps.
  • Outbox: Saves database records and outbox events in a single transaction, publishing events to Kafka via an independent publisher process to ensure delivery.
Interviewer Note: 💡 Essential for large-scale systems design interviews.

Architect Question #80: Design a scalable real-time notification engine supporting push, SMS, and email.

Level: Architect | Category: Design Patterns | Time: 15 mins

Expected Answer

A scalable notification platform uses an event-driven architecture based on **Apache Kafka** or **RabbitMQ** combined with BullMQ processing layers. Key stages include:

  1. Ingestion API: Express endpoint validates requests and immediately pushes a job event (e.g. SendNotification) into a message broker, returning status 202 accepted.
  2. Broker (Kafka): Partitions notification events by user or category to scale consumers.
  3. Consumers (BullMQ): Specialized worker groups subscribe to events, retrieve user profile settings, apply template designs, and call third-party services (Twilio, Sendgrid) with exponential retries.
Interviewer Note: 💡 Architect should explain how to implement rate limits per provider so target APIs don't block the workers.

Frequently Asked Questions

Is Node.js still relevant in 2026?

Yes. Node.js remains one of the most popular backend technologies for APIs, microservices and real-time applications.

Do companies ask event loop questions?

Yes. Event Loop, Streams, Async Programming and Performance are very common Node.js interview topics.

Are coding questions included?

Yes. Practical coding and debugging scenarios are included.