Toolmingo
Guides21 min read

50 Node.js Interview Questions (With Answers)

Top Node.js interview questions with clear answers and code examples — covering the event loop, streams, modules, async patterns, clustering, and real-world architecture.

Node.js interviews test event-loop intuition, async-programming depth, built-in module knowledge, and backend architecture skills. This guide covers the 50 most common questions — with concise answers and runnable code examples.

Quick reference

Topic Most asked questions
Architecture Event loop, non-blocking I/O, single thread
Async Callbacks, Promises, async/await, EventEmitter
Modules CommonJS vs ES modules, require caching
Core modules fs, path, http, stream, crypto, child_process
Performance Clustering, worker threads, memory leaks
Ecosystem npm, package.json, Express, error handling

Architecture

1. What is Node.js and how does it differ from browser JavaScript?

Node.js is a server-side JavaScript runtime built on V8 (Chrome's JS engine) and libuv (a C library that provides the event loop and async I/O).

Feature Node.js Browser JS
Environment Server Browser sandbox
APIs fs, http, process, os DOM, fetch, localStorage
Modules CommonJS + ESM ESM (+ legacy <script>)
Security Full OS access Sandboxed
Global object global / globalThis window / globalThis
// Node.js — read a file
const fs = require('fs');
const data = fs.readFileSync('hello.txt', 'utf8');
console.log(data);

2. Explain the Node.js event loop.

The event loop lets Node.js perform non-blocking I/O with a single thread by offloading operations to the OS or libuv thread pool, then executing callbacks when they complete.

Phases (each iteration / "tick"):

  1. timers — executes setTimeout / setInterval callbacks whose delay has elapsed
  2. pending callbacks — I/O callbacks deferred from the previous loop
  3. idle/prepare — internal use
  4. poll — retrieves new I/O events; blocks here if queue is empty
  5. check — executes setImmediate callbacks
  6. close callbacks — e.g. socket.on('close', ...)

Between each phase, Node drains the microtask queue (Promise callbacks, queueMicrotask) and process.nextTick queue.

console.log('1 — sync');

setTimeout(() => console.log('4 — timer'), 0);
setImmediate(() => console.log('5 — check'));

Promise.resolve().then(() => console.log('3 — microtask'));
process.nextTick(() => console.log('2 — nextTick'));

// Output: 1 → 2 → 3 → 4 → 5

3. What does "non-blocking I/O" mean?

When Node.js starts an I/O operation (reading a file, querying a DB, making an HTTP request), it does not wait for the result. It registers a callback and continues executing other code. The callback runs when the OS signals completion.

const fs = require('fs');

// Non-blocking — callback fires when OS delivers data
fs.readFile('big.log', 'utf8', (err, data) => {
  console.log('file ready', data.length);
});

console.log('this runs FIRST');

The thread pool (default 4 workers, set UV_THREADPOOL_SIZE) handles tasks that can't be done asynchronously by the OS, such as DNS lookups, crypto, and fs operations on some platforms.


4. What is process.nextTick vs setImmediate vs setTimeout(fn, 0)?

When it runs Microtask?
process.nextTick(cb) Before any I/O, before Promise microtasks Yes (own queue, highest priority)
Promise.then(cb) After nextTick queue is empty Yes
setImmediate(cb) After poll phase (check phase) No
setTimeout(cb, 0) timers phase, ≥ 1 ms delay No

Rule of thumb: nextTick is fastest but can starve I/O if called recursively. setImmediate is preferred for deferring work after I/O.

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

// nextTick → Promise → setTimeout → setImmediate
// (timer vs setImmediate order can vary outside I/O callbacks)

5. Is Node.js truly single-threaded?

The JS execution is single-threaded. But Node.js uses multiple threads internally:

  • libuv thread pool (default 4 threads) — handles fs, dns.lookup, crypto, zlib
  • V8 garbage collector — runs on a background thread
  • Worker threads (worker_threads module) — user-created threads for CPU-bound work

So "single-threaded" refers to the JS event loop, not the entire process.


Modules

6. What is the difference between CommonJS and ES modules?

Feature CommonJS (CJS) ES Modules (ESM)
Syntax require() / module.exports import / export
Loading Synchronous, dynamic Asynchronous, static
Top-level await No Yes
Tree shaking No Yes
File extension .js (default) .mjs or "type":"module" in package.json
__dirname Available Use import.meta.url
// CJS
const path = require('path');
module.exports = { greet };

// ESM
import path from 'path';
export { greet };

7. How does require() caching work?

The first time you require() a module, Node executes it and caches the result in require.cache. Subsequent calls return the cached export — the module code does not run again.

// counter.js
let count = 0;
module.exports = { increment: () => ++count, get: () => count };

// main.js
const a = require('./counter');
const b = require('./counter'); // same cached object
a.increment();
console.log(b.get()); // 1 — same reference

To bypass caching (rare): delete require.cache[require.resolve('./counter')]


8. What is the module.exports vs exports gotcha?

exports is a reference to module.exports. Reassigning exports breaks the link.

// WRONG — exports is now a new object; module.exports is still {}
exports = { greet: () => 'hi' };

// CORRECT
module.exports = { greet: () => 'hi' };
// OR
exports.greet = () => 'hi'; // mutating the shared object is fine

9. How do you get __dirname in an ES module?

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname  = dirname(__filename);

Or with the newer Node.js 21+ API:

import { dirname } from 'path';
const __dirname = dirname(import.meta.filename); // Node ≥ 21.2

Async patterns

10. What is callback hell and how do you avoid it?

Callback hell (pyramid of doom) is deeply nested callbacks that make code hard to read and reason about.

// callback hell
fs.readFile('a.txt', (err, a) => {
  fs.readFile('b.txt', (err, b) => {
    fs.readFile('c.txt', (err, c) => {
      // ...
    });
  });
});

Fixes:

  1. Named functions — extract callbacks into named functions
  2. Promises — chain .then() calls
  3. async/await — read like synchronous code
// async/await
async function readAll() {
  const a = await fs.promises.readFile('a.txt', 'utf8');
  const b = await fs.promises.readFile('b.txt', 'utf8');
  const c = await fs.promises.readFile('c.txt', 'utf8');
  return a + b + c;
}

11. How do you run async operations in parallel in Node.js?

Use Promise.all for parallel execution. await inside a loop runs sequentially.

// SEQUENTIAL — slow
for (const url of urls) {
  const res = await fetch(url); // waits for each
}

// PARALLEL — fast
const results = await Promise.all(urls.map(url => fetch(url)));

For bounded concurrency (avoid overwhelming a server):

import pLimit from 'p-limit';
const limit = pLimit(5); // max 5 concurrent

const results = await Promise.all(
  urls.map(url => limit(() => fetch(url)))
);

12. What is the EventEmitter class?

EventEmitter is the foundation for Node.js's event-driven architecture. Many core objects (http.Server, fs.ReadStream, net.Socket) extend it.

const { EventEmitter } = require('events');

class OrderService extends EventEmitter {
  place(order) {
    // process order...
    this.emit('order:placed', order);
  }
}

const svc = new OrderService();
svc.on('order:placed', (order) => console.log('Email sent for', order.id));
svc.once('order:placed', () => console.log('First order!')); // fires only once

svc.place({ id: 42 });

Memory leak warning: Node emits a warning if you add more than 10 listeners to one event. Increase with emitter.setMaxListeners(n).


13. What is util.promisify?

Converts a Node-style callback function (err, result) => void into a Promise-returning function.

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

const readFile = promisify(fs.readFile);

const data = await readFile('hello.txt', 'utf8');

Modern Node.js ships promise-based APIs natively: fs.promises, dns.promises, etc.


Core modules

14. How do you create an HTTP server without Express?

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok' }));
    return;
  }
  res.writeHead(404);
  res.end('Not found');
});

server.listen(3000, () => console.log('Listening on :3000'));

15. What are Node.js streams and what types exist?

Streams process data piece by piece rather than loading everything into memory — essential for large files, HTTP requests, and real-time data.

Type Description Example
Readable Source of data fs.createReadStream, http.IncomingMessage
Writable Destination fs.createWriteStream, http.ServerResponse
Duplex Readable + Writable net.Socket, crypto.Cipher
Transform Duplex that modifies data zlib.createGzip, crypto.createCipheriv
const fs = require('fs');
const zlib = require('zlib');

// Pipe a large file through gzip compression to output — minimal memory usage
fs.createReadStream('bigfile.csv')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('bigfile.csv.gz'));

16. What is backpressure in streams?

Backpressure occurs when a Writable can't keep up with a Readable. If ignored, data buffers in memory and can crash the process.

pipe() handles backpressure automatically by pausing the readable when the writable's internal buffer is full.

When using streams manually:

readable.on('data', (chunk) => {
  const ok = writable.write(chunk);
  if (!ok) {
    readable.pause();                   // stop producing
    writable.once('drain', () => {
      readable.resume();                // resume when writable is ready
    });
  }
});

17. How do you read and write files in Node.js?

const fs = require('fs/promises'); // promise-based (Node ≥ 10)

// Read
const content = await fs.readFile('data.txt', 'utf8');

// Write (overwrites)
await fs.writeFile('out.txt', 'hello');

// Append
await fs.appendFile('log.txt', 'new line\n');

// Stream (large files)
const readable = require('fs').createReadStream('big.csv');
for await (const chunk of readable) {
  process(chunk);
}

18. What does the path module provide?

Platform-safe path manipulation — essential for cross-platform code (Windows uses \, POSIX uses /).

const path = require('path');

path.join('/users', 'alice', 'docs');        // /users/alice/docs
path.resolve('src', 'index.js');             // absolute from CWD
path.dirname('/users/alice/file.txt');       // /users/alice
path.basename('/users/alice/file.txt');      // file.txt
path.extname('/users/alice/file.txt');       // .txt
path.parse('/users/alice/file.txt');
// { root: '/', dir: '/users/alice', base: 'file.txt', ext: '.txt', name: 'file' }

19. How do you hash a password in Node.js?

Use crypto.scrypt (built-in) or the bcrypt / argon2 packages. Never use MD5 or SHA for passwords.

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

const scrypt = promisify(crypto.scrypt);

async function hashPassword(password) {
  const salt = crypto.randomBytes(16).toString('hex');
  const key  = await scrypt(password, salt, 64);
  return `${salt}:${key.toString('hex')}`;
}

async function verify(password, stored) {
  const [salt, hash] = stored.split(':');
  const key = await scrypt(password, salt, 64);
  return crypto.timingSafeEqual(Buffer.from(hash, 'hex'), key);
}

20. How do you spawn a child process?

const { exec, execFile, spawn, fork } = require('child_process');

// exec — runs in a shell, buffers output (small output only)
exec('ls -la', (err, stdout, stderr) => console.log(stdout));

// spawn — streaming, no shell, safe for user input
const ls = spawn('ls', ['-la']);
ls.stdout.pipe(process.stdout);

// fork — spawn a Node.js child with IPC channel
const child = fork('./worker.js');
child.send({ task: 'compute' });
child.on('message', (result) => console.log(result));

Use spawn (not exec) when input comes from users to avoid shell injection.


Express and HTTP

21. What is Express middleware and how does it work?

Middleware is a function with signature (req, res, next). Express chains middleware left to right; calling next() passes control to the next one.

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

// Application-level middleware
app.use(express.json());

// Custom logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // must call next or send a response
});

// Route handler
app.get('/users', (req, res) => res.json([]));

// Error-handling middleware — 4 parameters
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: err.message });
});

app.listen(3000);

22. What is app.use vs app.get?

app.use(path, fn) app.get(path, fn)
HTTP method Any method GET only
Path matching Prefix match (/api matches /api/users) Exact match
Use case Middleware, routers Route handlers

23. How do you handle errors in Express async route handlers?

Express doesn't catch Promise rejections automatically (v4). You must either:

Option A — wrapper:

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

app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) throw Object.assign(new Error('Not found'), { status: 404 });
  res.json(user);
}));

Option B — Express 5 (handles async natively):

// npm install express@5
app.get('/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id); // rejection auto-forwarded to error handler
  res.json(user);
});

24. What is CORS and how do you enable it in Express?

Cross-Origin Resource Sharing — browsers block requests from one origin to another by default. The server opts in via response headers.

const cors = require('cors');

// Allow all origins (dev only)
app.use(cors());

// Production: specific origin + credentials
app.use(cors({
  origin: 'https://app.example.com',
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

For preflight (OPTIONS) requests, Express's cors() middleware handles them automatically.


25. How do you parse the request body in Express?

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

app.post('/data', (req, res) => {
  console.log(req.body); // already parsed
  res.json({ received: req.body });
});

For file uploads, use multer:

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

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

Error handling

26. What is the difference between operational errors and programmer errors?

Operational errors Programmer errors
Cause Expected runtime conditions: network failure, invalid user input, file not found Bugs: TypeError, wrong API usage, logic errors
Handling Catch and handle gracefully (send 400/500, retry, log) Let it crash and restart (PM2/nodemon), fix the code
Examples ECONNREFUSED, ENOENT, 404 Cannot read properties of undefined

27. How do you handle uncaught exceptions and unhandled rejections?

// Last resort — avoid relying on these
process.on('uncaughtException', (err) => {
  console.error('Uncaught exception:', err);
  process.exit(1); // MUST exit — process state is undefined
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('Unhandled rejection at:', promise, 'reason:', reason);
  // In Node.js 15+, this crashes by default (good behaviour)
  process.exit(1);
});

Better: always await or .catch() promises; use a global error handler in Express.


28. How do you implement graceful shutdown?

const server = app.listen(3000);

async function shutdown(signal) {
  console.log(`${signal} received — shutting down`);
  server.close(async () => {
    await db.end();          // close DB pool
    process.exit(0);
  });
  // Force-kill after 10 s if connections don't drain
  setTimeout(() => process.exit(1), 10_000).unref();
}

process.on('SIGTERM', () => shutdown('SIGTERM')); // Kubernetes sends this
process.on('SIGINT',  () => shutdown('SIGINT'));  // Ctrl+C

Performance and scaling

29. What is the cluster module?

Node.js runs on a single CPU core. The cluster module forks multiple worker processes, each running the same server code on all available cores.

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

if (cluster.isPrimary) {
  const cpus = os.cpus().length;
  console.log(`Primary ${process.pid} — forking ${cpus} workers`);
  for (let i = 0; i < cpus; i++) cluster.fork();
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.pid} died — restarting`);
    cluster.fork();
  });
} else {
  http.createServer((req, res) => {
    res.end(`Worker ${process.pid} responded`);
  }).listen(3000);
}

The OS load-balances incoming connections across workers.


30. What are Worker Threads and when do you use them?

worker_threads lets you run JavaScript in parallel threads — useful for CPU-intensive work that would block the event loop (image processing, JSON parsing of huge files, ML inference).

// main.js
const { Worker } = require('worker_threads');

const worker = new Worker('./cpu-task.js', { workerData: { n: 40 } });
worker.on('message', (result) => console.log('Fibonacci:', result));
worker.on('error', console.error);

// cpu-task.js
const { workerData, parentPort } = require('worker_threads');
function fib(n) { return n < 2 ? n : fib(n-1) + fib(n-2); }
parentPort.postMessage(fib(workerData.n));

Cluster vs Worker Threads:

  • Cluster — multiple processes, each with own event loop, for I/O-bound horizontal scaling
  • Worker Threads — multiple threads in one process sharing memory, for CPU-bound tasks

31. What causes memory leaks in Node.js and how do you detect them?

Common causes:

  • Global variables holding growing data
  • Event listeners not removed (emitter.off())
  • Closures capturing large objects
  • Uncleared timers (setInterval without clearInterval)
  • Cache with no eviction policy

Detection:

// Heap snapshot via --inspect flag, then Chrome DevTools > Memory
node --inspect app.js

// Log memory usage
setInterval(() => {
  const { rss, heapUsed, heapTotal } = process.memoryUsage();
  console.log({ rss: rss >> 20, heapUsed: heapUsed >> 20 });
}, 5000);

Tools: clinic.js, heapdump, 0x flame graphs, --inspect + Chrome DevTools allocation timeline.


32. What is the Node.js thread pool and how do you configure it?

libuv maintains a thread pool (default 4 threads) for operations that can't be async at the OS level: fs on some platforms, dns.lookup, crypto, zlib.

UV_THREADPOOL_SIZE=16 node server.js

If your app does heavy crypto or fs work, increasing this reduces queuing. Max is 128 (1024 in Node 22+).


npm and packages

33. What is the difference between dependencies and devDependencies?

dependencies devDependencies
When installed Always (npm install) Only in dev (npm install, not npm install --omit=dev)
Examples express, axios, prisma jest, eslint, typescript, nodemon
In production Docker Include Exclude with npm ci --omit=dev

34. What is npm ci and when should you use it?

npm ci installs exactly what's in package-lock.json, failing if lock and package.json diverge. It deletes node_modules first.

Use it in CI/CD and Docker for reproducible, faster installs.

COPY package.json package-lock.json ./
RUN npm ci --omit=dev   # faster, deterministic, no devDeps

35. What is semantic versioning (semver)?

MAJOR.MINOR.PATCH — e.g. 4.18.2

Symbol Meaning Example
^4.18.2 ≥ 4.18.2 and < 5.0.0 (compatible minor/patch) default npm install
~4.18.2 ≥ 4.18.2 and < 4.19.0 (patch only) conservative
4.18.2 Exact version locked
* Any version dangerous

Security

36. How do you prevent command injection in Node.js?

Never pass user input to exec() (which runs in a shell). Use execFile() or spawn() with an args array.

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

// SAFE — no shell, args are separate
execFile('convert', [userProvidedFilename, 'out.png'], (err, stdout) => {
  // ...
});

// DANGEROUS — shell interprets special chars
exec(`convert ${userProvidedFilename} out.png`); // rm -rf if filename is "x; rm -rf /"

37. What is path traversal and how do you prevent it?

Attackers supply ../../etc/passwd as a filename parameter to read arbitrary files.

const path = require('path');
const BASE = path.resolve('./uploads');

function safeRead(userFilename) {
  const resolved = path.resolve(BASE, userFilename);
  if (!resolved.startsWith(BASE + path.sep)) {
    throw new Error('Access denied');
  }
  return fs.promises.readFile(resolved);
}

38. How do you store secrets in Node.js?

  1. Use environment variables — never hardcode in source
  2. Use .env files locally (with dotenv) — add .env to .gitignore
  3. In production — use a secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler)
require('dotenv').config();
const DB_URL = process.env.DATABASE_URL;
if (!DB_URL) throw new Error('DATABASE_URL is required');

Testing

39. How do you test Node.js applications?

Common stack: Jest or Vitest + Supertest for HTTP.

// users.test.js
const request = require('supertest');
const app = require('./app');

describe('GET /users/:id', () => {
  it('returns 200 for existing user', async () => {
    const res = await request(app).get('/users/1');
    expect(res.status).toBe(200);
    expect(res.body).toHaveProperty('name');
  });

  it('returns 404 for unknown user', async () => {
    const res = await request(app).get('/users/9999');
    expect(res.status).toBe(404);
  });
});

40. How do you mock modules in Jest?

jest.mock('./db', () => ({
  findUser: jest.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}));

const { findUser } = require('./db');

test('returns user', async () => {
  const user = await findUser(1);
  expect(user.name).toBe('Alice');
  expect(findUser).toHaveBeenCalledWith(1);
});

Advanced topics

41. What is AsyncLocalStorage and what is it used for?

AsyncLocalStorage (Node.js 16+) stores context that flows through the entire async call chain without threading it through every function argument. Ideal for request-scoped data like user IDs, trace IDs, and loggers.

const { AsyncLocalStorage } = require('async_hooks');
const storage = new AsyncLocalStorage();

// Express middleware — set context at request entry
app.use((req, res, next) => {
  const ctx = { requestId: req.headers['x-request-id'] ?? crypto.randomUUID() };
  storage.run(ctx, next); // all async work inside next() sees this store
});

// Deep utility function — access without parameter drilling
function log(msg) {
  const ctx = storage.getStore();
  console.log(`[${ctx?.requestId}] ${msg}`);
}

42. What is the difference between Buffer and Uint8Array?

Buffer is a Node.js subclass of Uint8Array with extra convenience methods for encoding/decoding.

// Create
const buf = Buffer.from('hello', 'utf8');
const hex = buf.toString('hex');       // 68656c6c6f
const b64 = buf.toString('base64');    // aGVsbG8=

// Allocation
Buffer.alloc(10);       // zero-filled (safe)
Buffer.allocUnsafe(10); // faster but uninitialized (may contain old data)

// Concatenate
const result = Buffer.concat([buf1, buf2]);

Use Buffer for binary protocols, file content, crypto, and network data.


43. How does Node.js handle the require of a JSON file?

require('./config.json') parses the JSON and returns a plain JS object. It's cached like any other module.

const config = require('./config.json'); // { port: 3000, debug: true }
console.log(config.port); // 3000

In ESM, use import with assert { type: 'json' } (or with { type: 'json' } in newer Node):

import config from './config.json' with { type: 'json' };

44. What is libuv and what role does it play?

libuv is the C library that powers Node.js's event loop, async I/O, timers, and the thread pool. It provides a platform-agnostic interface to OS async APIs (epoll on Linux, kqueue on macOS, IOCP on Windows).

Key responsibilities:

  • Event loop phases and scheduling
  • Thread pool for blocking operations
  • Timers (setTimeout, setInterval)
  • TCP/UDP sockets, DNS, file system, child processes, signal handling

45. What is __proto__ vs prototype in the Node.js context?

Applies to all JS, including Node.js:

function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return `${this.name} speaks`; };

const dog = new Animal('Rex');

// dog.__proto__ === Animal.prototype (true)
// Animal.prototype.constructor === Animal (true)
console.log(dog.speak()); // Rex speaks

// Modern way
Object.getPrototypeOf(dog) === Animal.prototype; // true

46. What is the net module used for?

Low-level TCP server and socket creation — useful for custom binary protocols, proxies, and testing.

const net = require('net');

const server = net.createServer((socket) => {
  socket.write('Welcome\n');
  socket.on('data', (data) => {
    socket.write(`Echo: ${data}`);
  });
  socket.on('end', () => console.log('Client disconnected'));
});

server.listen(8080, () => console.log('TCP server on :8080'));

47. How do you implement rate limiting in an Express app?

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

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,                  // max 100 requests per window per IP
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, slow down.' },
});

app.use('/api/', limiter);

For distributed systems, use a Redis store (rate-limit-redis) so limits are shared across cluster workers.


48. What is PM2 and why is it used?

PM2 is a production process manager for Node.js:

  • Auto-restart on crash
  • Cluster mode — starts one worker per CPU core automatically
  • Zero-downtime reload (pm2 reload app)
  • Logging and log rotation
  • Startup scripts (systemd/init.d)
pm2 start app.js -i max    # cluster mode, all cores
pm2 logs
pm2 monit
pm2 save && pm2 startup    # persist across reboots

49. Explain the "event-driven architecture" in Node.js.

Instead of polling or blocking, Node.js components emit events and react to them. The pattern decouples producers from consumers.

const { EventEmitter } = require('events');

class PaymentService extends EventEmitter {
  async charge(order) {
    const result = await stripe.charge(order);
    this.emit('payment:success', { order, result });
  }
}

const payments = new PaymentService();
payments.on('payment:success', ({ order }) => emailService.sendReceipt(order));
payments.on('payment:success', ({ order }) => analytics.track('purchase', order));
payments.on('payment:success', ({ order }) => inventory.decrement(order.items));

Benefits: open/closed principle (add listeners without modifying PaymentService), testability, and natural async flow.


50. What are the most common Node.js performance anti-patterns?

Anti-pattern Problem Fix
Sync fs methods in server Blocks event loop Use async fs.promises.*
JSON.parse of massive files Freezes event loop for seconds Use streaming JSON parser (stream-json)
No connection pooling New DB connection per request Use pg.Pool, mysql2/promise pool
Unbound setInterval Memory leak, prevents exit Store ref, call clearInterval on shutdown
Promise.all on 1000 items Overwhelms external APIs Use p-limit for bounded concurrency
Storing state in global vars (clustered) Workers don't share memory Use Redis or a DB for shared state
Catching errors silently Hidden bugs, no logs Always log + re-throw or propagate
Blocking in a hot path (crypto/bcrypt sync) Degrades all concurrent requests Use async API or offload to worker thread

Common mistakes

Mistake Correct approach
require inside a loop require is cached — still wasteful for clarity; require at top
Using == instead of === Always === in JS
Not handling rejected Promises Add .catch() or try/catch with await
forEach with async callback Use for...of + await or Promise.all(arr.map(...))
res.send() after res.end() Check res.headersSent or return early
Setting NODE_ENV=production wrong Use environment variables, not hardcoded strings
Mutating req.params / req.query They're read-only; copy into a variable
Blocking the event loop with crypto.pbkdf2Sync Use async variant or worker thread

Node.js vs Deno vs Bun

Feature Node.js Deno Bun
Runtime V8 + libuv V8 + Tokio (Rust) JavaScriptCore + Zig
Package manager npm built-in (URL imports + jsr) bun (compatible with npm)
TypeScript Requires tsx / ts-node Built-in Built-in
ESM Supported (v12+) Default Default
Permissions No sandboxing Explicit --allow-net etc. No sandboxing
Maturity Highest Stable Growing fast
Best for Production backends Secure scripting, edge Speed, monorepo tooling

FAQ

Q: Is Node.js good for CPU-intensive tasks?
A: Not directly — heavy computation blocks the event loop. Use Worker Threads, cluster, or offload to a separate microservice (Go/Rust/Python).

Q: When should I use async/await vs streams?
A: async/await for small payloads where buffering is fine. Streams for large payloads (files, audio, video) where you can't afford to buffer the whole thing in memory.

Q: What version of Node.js should I use in production?
A: Always an LTS (Long-Term Support) release. LTS versions receive security patches for 30 months. Check the current LTS at nodejs.org/en/about/previous-releases.

Q: How does Node.js compare to Go or Python for web APIs?
A: Node.js excels at I/O-bound workloads (APIs, real-time, proxies) with low CPU. Go is faster for CPU-bound work and concurrency. Python has a richer data science ecosystem. For a JSON REST API with DB calls, all three are similar in practice.

Q: How do I debug a Node.js app?
A: node --inspect app.js, then open chrome://inspect in Chrome. Or use VS Code's built-in debugger (.vscode/launch.json with "type": "node").

Q: What is global in Node.js?
A: The global object (equivalent to window in browsers). Variables declared at the top level with var in CJS modules are not on global — they're scoped to the module. Use global.myVar = x to actually set a global (generally avoid this).

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools