Node.
js Interview Handbook (5+ Years) — Deep
Dive
A practical, interview‑focused handbook with explanations, trade‑offs, and copy‑pasteable code for each
topic. Use the TL;DR boxes for quick answers; use the Deep Dive and Code sections when the
interviewer probes.
1) Core Node.js Concepts
1.1 Event Loop & Async Handling
TL;DR: Node.js is single‑threaded at the JS level but uses libuv for I/O. The event loop cycles through
phases (timers → pending callbacks → idle/prepare → poll → check → close). Microtasks (promises,
process.nextTick ) run between ticks.
Deep Dive:
• Non‑blocking I/O: disk/network operations are delegated to OS/libuv threads.
• CPU‑bound code blocks the loop → use Worker Threads or offload.
• Microtasks queue runs after the current call stack and before the next macrotask phase.
Code: Ordering demo
console.log('A');
setTimeout(() => console.log('B: setTimeout 0'), 0);
setImmediate(() => console.log('C: setImmediate'));
Promise.resolve().then(() => console.log('D: microtask (promise)'));
process.nextTick(() => console.log('E: nextTick (highest microtask)'));
console.log('F');
// Likely order: A, F, E, D, (B/C may swap depending on I/O state)
Interview Tip: Mention microtasks vs macrotasks and when the poll phase empties before check.
1.2 process.nextTick , setImmediate , setTimeout
TL;DR: nextTick runs before any other queued work; setImmediate runs in check phase;
setTimeout(…,0) runs in timers phase ≥ 0ms later.
Gotcha: Overusing nextTick can starve the event loop; prefer promises.
1
1.3 CJS vs ESM
TL;DR: CJS uses require/module.exports (sync). ESM uses import/export (async, static analysis,
tree‑shaking). In Node, enable with "type":"module" or .mjs .
// CommonJS
const fs = require('fs');
module.exports = { read: fs.readFileSync };
// ESM
import fs from 'fs';
export const read = fs.readFileSync;
Migration Tip: Avoid mixing; if you must, use dynamic import() in CJS or createRequire in ESM.
1.4 Cluster & Multi‑Core
TL;DR: Cluster forks multiple Node processes to utilize all cores; each has its own event loop and
memory. Use a load balancer (Node’s master or external like NGINX/PM2).
// cluster-demo.js
import cluster from 'cluster';
import http from 'http';
import os from 'os';
if (cluster.isPrimary) {
os.cpus().forEach(() => cluster.fork());
cluster.on('exit', (w) => cluster.fork()); // self-heal
} else {
http.createServer((_, res) => res.end(`pid ${process.pid}`)).listen(3000);
}
When to use: High concurrency I/O workloads. Not for sharing in‑memory state (use Redis/DB).
1.5 Child Processes
TL;DR: Use child_process for shell commands ( exec , spawn ) or to isolate tasks.
import { spawn, exec } from 'child_process';
exec('ls -la', (err, out) => { if (!err) console.log(out); });
const child = spawn('node', ['worker.js']);
child.stdout.on('data', d => process.stdout.write(d));
Use cases: Image processing, ffmpeg, calling legacy tools.
2
2) Asynchronous Programming
2.1 Callbacks vs Promises vs Async/Await
TL;DR: Callbacks are simple but nest; Promises chain; async/await is readable on top of promises.
// Promise -> async/await wrapper
const getJson = (url) => fetch(url).then(r => r.json());
(async () => {
try { console.log(await getJson('https://api.github.com')); }
catch (e) { console.error(e); }
})();
Pitfall: Don’t forget await on promises; handle rejections.
2.2 Microtasks vs Macrotasks
TL;DR: Microtasks (promises, nextTick ) run before macrotasks (timeouts, I/O). Use microtasks for
follow‑up work that must run ASAP.
2.3 Single Thread Concurrency
TL;DR: Concurrency comes from non‑blocking I/O. Use Worker Threads for CPU work.
2.4 Worker Threads vs Cluster
TL;DR: Workers share memory (transfer/SharedArrayBuffer) → CPU jobs; Cluster = processes → scale
HTTP.
// worker-demo.js
import { Worker } from 'worker_threads';
const worker = new Worker(new URL('./fib-worker.mjs', import.meta.url));
worker.postMessage(40);
worker.on('message', (n) => console.log('fib(40)=', n));
2.5 Streams
TL;DR: Process data in chunks: less memory, backpressure support.
import fs from 'fs';
fs.createReadStream('big.csv')
3
.pipe(fs.createWriteStream('copy.csv'))
.on('finish', () => console.log('done'));
Use cases: File copy, gzip, HTTP proxying.
3) Error Handling & Debugging
3.1 Async/Await Best Practices
• Wrap with try/catch near boundaries (controller layer).
• Centralize Express error handling middleware.
// express-error.js
app.use(async (req, res, next) => {
try { await next(); }
catch (err) { next(err); }
});
app.use((err, req, res, _next) => {
console.error(err);
res.status(500).json({ message: 'Internal error' });
});
3.2 Operational vs Programmer Errors
• Operational: timeouts, ECONNREFUSED → retry, circuit break.
• Programmer: undefined var, bad logic → crash fast, fix code.
3.3 Debugging Memory Leaks
• Use node --inspect → Chrome DevTools → Heap snapshots.
• Tools: clinic.js, 0x, heapdump.
3.4 uncaughtException & unhandledRejection
process.on('unhandledRejection', (r) => { console.error(r); /* alert/metrics
*/ });
process.on('uncaughtException', (e) => {
console.error(e);
// attempt graceful shutdown; let process manager (PM2/K8s) restart
process.exit(1);
});
4
4) Performance & Scaling
4.1 Multi‑CPU Scaling
• Use Cluster/PM2, or run multiple containers/pods behind a load balancer.
4.2 Load Balancing Strategies
• Round‑robin, least‑connections (NGINX/ELB), sticky sessions (if using in‑memory sessions →
better: Redis store).
4.3 Detect & Fix Event Loop Blocking
• Symptoms: high latency under load with low CPU usage per worker.
• Tools: clinic doctor , 0x , —prof .
• Fix: move CPU work to Workers, ensure async DB calls, paginate.
4.4 High Throughput API Techniques
• HTTP keep‑alive, gzip/br, caching (CDN/Redis), batching, idempotency keys, pagination,
connection pooling.
Code: Basic Redis cache
import express from 'express';
import Redis from 'ioredis';
const app = express();
const redis = new Redis();
app.get('/users/:id', async (req, res) => {
const key = `user:${req.params.id}`;
const cached = await redis.get(key);
if (cached) return res.json(JSON.parse(cached));
const user = await db.users.findById(req.params.id);
await redis.setex(key, 60, JSON.stringify(user));
res.json(user);
});
5) Security
5.1 Common Risks & Mitigations
• XSS: Escape output, CSP, sanitize input.
• SQLi/NoSQLi: Use parameterized queries; validate types.
• CSRF: SameSite cookies, CSRF tokens.
• DOS: Rate limit, timeouts, body size limits.
Express hardening
5
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
app.use(helmet());
app.use(rateLimit({ windowMs: 60_000, max: 100 }));
app.use(express.json({ limit: '1mb' }));
5.2 Secrets Management
• .env for local; production: AWS Secrets Manager, Vault, GCP Secret Manager. Never commit
secrets.
6) Databases
6.1 Connection Pooling
Postgres
import { Pool } from 'pg';
export const pool = new Pool({ max: 10, idleTimeoutMillis: 30_000 });
MongoDB
import { MongoClient } from 'mongodb';
const client = new MongoClient(process.env.MONGO_URL); // driver manages pool
await client.connect();
6.2 ORM/ODM vs Raw
• ORM/ODM (Prisma/Sequelize/Mongoose): faster dev, migrations, validation.
• Raw: maximum control/perf, less abstraction.
6.3 Transactions
Postgres
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('INSERT INTO accounts ...');
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally { client.release(); }
6
MongoDB (replica set/Atlas required)
const session = client.startSession();
try {
await session.withTransaction(async () => {
await users.insertOne({ name: 'A' }, { session });
await orders.insertOne({ user: 'A' }, { session });
});
} finally { await session.endSession(); }
7) Testing & CI/CD
7.1 Unit/Integration Testing
// Jest unit test example
import { sum } from './math.js';
test('sum', () => expect(sum(2,3)).toBe(5));
// Integration with supertest
import request from 'supertest';
import app from '../app.js';
test('GET /health', async () => {
await request(app).get('/health').expect(200);
});
7.2 Async Testing Patterns
test('await pattern', async () => {
const data = await fetchData();
expect(data.ok).toBe(true);
});
7.3 CI/CD Outline
• Pipeline: lint → test → build → scan → deploy.
• Tools: GitHub Actions, GitLab CI, Jenkins; deployments via Docker/K8s.
8) Architecture & Best Practices
8.1 SOLID in Node
• SRP: small modules (authService, userRepo).
7
• OCP: middleware/plugins; extend without modify.
• LSP: repository interfaces interchangeable (Mongo/Postgres).
• ISP: split large interfaces; focused services.
• DIP: depend on abstractions; inject implementations.
DIP Example (logger)
class Service {
constructor(logger) { this.logger = logger; }
run() { this.logger.info('running'); }
}
// main.js decides which logger to use
8.2 Singleton
• Good: DB pool, config loader.
• Risks: global state, testing pain, multi‑process inconsistency. Prefer DI; if used, keep stateless or
idempotent.
8.3 Monolith vs Microservices
• Monolith: simpler deploy, easier transactions; great to start.
• Microservices: independent scaling, team boundaries; added ops complexity.
8.4 Stateless API
• No in‑memory sessions; JWT or session store (Redis). Store files in S3/GCS.
8.5 Queues (Kafka/RabbitMQ/BullMQ)
• Use for async tasks: email, billing, heavy processing. Improves latency and resilience.
9) Real‑World Scenarios
9.1 API Latency Spike
Checklist:
• Check event loop lag ( event-loop-lag ), DB slow queries, GC pauses, hot code paths.
• Add caching, indexes, pagination; move CPU tasks to workers.
9.2 Random Crashes
• Inspect logs/traces; ensure proper error boundaries; use PM2/K8s restart policies; add health
checks.
8
9.3 Rate Limiter Middleware (Redis)
import Redis from 'ioredis';
const redis = new Redis();
export const rateLimit = (limit=100, windowSec=60) => async (req,res,next)
=>