Master Node.js interviews with 50+ questions on event loop, streams, performance, and backend architecture.
10 Questions
~30 min read
The event loop handles async operations in phases: (1) Timers - setTimeout/setInterval callbacks, (2) Pending callbacks - I/O callbacks deferred, (3) Idle/Prepare - internal use, (4) Poll - retrieve new I/O events, (5) Check - setImmediate callbacks, (6) Close callbacks - socket.on("close"). process.nextTick and Promises run between phases in microtask queue.
Buffer handles raw binary data outside the V8 heap. Used for: file I/O, network protocols, encryption, image processing. Create with Buffer.from(), Buffer.alloc(). Buffers are fixed-size; use streams for large data. Security: use alloc() not allocUnsafe() unless you'll immediately fill it. Convert with toString(), toJSON().
process.nextTick() executes immediately after current operation, before the event loop continues - it's in the microtask queue. setImmediate() executes in the Check phase of the event loop. nextTick has higher priority and can starve the event loop if used recursively. Use setImmediate for most cases; nextTick for critical callbacks that must run immediately.
For callbacks: error-first pattern (err, result). For Promises: .catch() or try/catch with async/await. For EventEmitters: "error" event listener. For streams: "error" event. Global handlers: process.on("uncaughtException") and process.on("unhandledRejection"). Best practice: always handle errors explicitly, use error boundaries, log and monitor.
Streams process data in chunks without loading everything into memory. Types: (1) Readable - data source (fs.createReadStream), (2) Writable - data destination (fs.createWriteStream), (3) Duplex - both readable/writable (TCP socket), (4) Transform - modify data while passing through (zlib.createGzip). Use pipe() to connect streams. Great for large files, real-time data.
Clustering creates child processes (workers) that share the same server port, utilizing multiple CPU cores. The master process manages workers and distributes connections. Use for: CPU-intensive applications, improving throughput, high availability. Workers don't share memory; use Redis or IPC for shared state. PM2 provides built-in cluster management.
Worker Threads enable true parallelism for CPU-intensive tasks by running JavaScript in separate threads. Unlike child_process, they share memory via SharedArrayBuffer. Use for: heavy computations, image processing, data parsing. Don't use for I/O-bound tasks (use async). Communication via postMessage(). Each worker has its own event loop.
CommonJS (require): synchronous loading, caches modules in require.cache, circular dependencies return partial exports. ES Modules (import): async loading, static analysis enables tree-shaking, live bindings. Node.js supports both; use .mjs extension or "type": "module" in package.json for ESM. Prefer ESM for new projects.
Middleware functions have access to request, response, and next(). They execute sequentially and can: modify req/res, end the request cycle, or call next(). Types: application-level (app.use), router-level (router.use), error-handling (4 params), built-in (express.json), third-party (cors, helmet). Order matters - declare before routes that use them.
Key practices: (1) Validate and sanitize input, (2) Use helmet for security headers, (3) Implement rate limiting, (4) Use HTTPS, (5) Keep dependencies updated (npm audit), (6) Don't expose stack traces in production, (7) Use parameterized queries for databases, (8) Implement proper authentication (JWT, sessions), (9) Set secure cookie flags, (10) Use environment variables for secrets.