Node.js is the dominant backend runtime for JavaScript developers at Indian product startups and mid-size companies. Interviews at Razorpay, CRED, Zepto, and other Indian startups test the event loop deeply, async patterns (callbacks, Promises, async/await), Express.js middleware, REST API design, and Node.js performance under load. This guide covers the concepts most frequently tested, with the specific question formats used in Indian interviews.
The Node.js Event Loop: What Indian Interviewers Actually Ask
The event loop is the single most tested Node.js concept in Indian interviews. The question is always paired with code: 'What is the output of this code?' with a mix of setTimeout, setImmediate, process.nextTick, and Promise.then.
The mental model: Node.js uses a single-threaded call stack. When the call stack is empty, the event loop picks the next callback from a queue. Queues in priority order: (1) process.nextTick queue (empties completely before anything else), (2) Promise microtask queue (empties completely), (3) timers phase (setTimeout/setInterval callbacks whose delay has elapsed), (4) I/O callbacks, (5) check phase (setImmediate callbacks).
Classic output question: console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); process.nextTick(() => console.log('4')); console.log('5'); Output: 1, 5, 4, 3, 2. Reason: synchronous runs first (1, 5), then nextTick (4), then Promise microtask (3), then timer (2).
setImmediate vs setTimeout(fn, 0): setImmediate fires in the check phase of the current iteration; setTimeout(fn, 0) fires at the start of the next iteration's timers phase. In I/O callbacks, setImmediate always fires before setTimeout(fn, 0). Outside I/O, the order is non-deterministic.
Async Patterns: Callbacks, Promises, and async/await
Callback hell: deeply nested callbacks (pyramid of doom) creating code that is hard to read, debug, and handle errors in. Solution: Promises or async/await.
Promise combinators: Promise.all (resolves when ALL resolve, rejects immediately on first rejection), Promise.race (resolves or rejects on the FIRST settled), Promise.allSettled (waits for ALL to settle regardless of outcome, returns [{status, value/reason}]), Promise.any (resolves on first FULFILLED, rejects with AggregateError if all reject).
async/await internals: async functions always return a Promise. await suspends the async function (yields control back to the event loop) until the awaited Promise settles. It does not block the event loop; other callbacks can run while waiting.
Error handling patterns: try/catch around await blocks for synchronous-style error handling; or attach .catch() to the returned Promise. Unhandled Promise rejections in Node.js 15+ terminate the process by default (process.on('unhandledRejection') to catch them).
Common async bug: for await vs Promise.all: running N async operations in a for loop with await runs them serially (N * averagetime total). Running them with Promise.all([ops]) runs them concurrently (averagetime total). Use Promise.all when operations are independent.
Express.js and REST API Design Questions
Middleware chain: app.use(fn) adds fn to the middleware stack. Each middleware runs in order for every matching request. Middleware calls next() to pass control to the next handler, calls next(err) to skip to the error handler, or ends the response. Order matters: error-handling middleware (4-argument: err, req, res, next) must be defined last.
Routing: app.use('/prefix') matches any method and any path starting with /prefix. app.get('/path') matches only GET and exactly /path (or /path/ with strict routing off). Router (express.Router()) creates modular route handlers that can be mounted with app.use().
REST design principles tested: idempotency (GET, PUT, DELETE: calling multiple times produces the same result; POST and PATCH are not idempotent by definition), correct HTTP status codes (201 for resource created, 204 for success with no body, 400 for bad request, 401 for unauthenticated, 403 for unauthorised, 404 for not found, 409 for conflict, 422 for validation error, 429 for rate limited, 500 for server error), versioning strategies (/v1/resource in URL vs Accept header vs query param).
JWT authentication in Express: middleware extracts the Bearer token from Authorization header, verifies signature with jsonwebtoken.verify(), attaches the decoded payload to req.user, calls next() if valid or next(401 error) if invalid. Routes that require auth use this middleware.
Practise Node.js developer interviews with HireStepX's AI voice interviewer. Get scored feedback on event loop, async patterns, and system design answers. First 2 sessions free.
Practice freeNode.js Performance and Scaling Questions
CPU-bound vs I/O-bound: Node.js handles I/O-bound work (database queries, HTTP calls, file reads) efficiently via non-blocking async I/O. CPU-bound work (image processing, cryptography, JSON parsing of large payloads) blocks the event loop because JavaScript execution is single-threaded. Solutions for CPU-bound: workerthreads module (shared ArrayBuffer for low-copy data transfer), childprocess.fork() (separate V8 instance), or a separate service written in a CPU-efficient language.
Clustering: Node's cluster module forks worker processes (one per CPU core). A master process distributes incoming connections across workers. PM2 in cluster mode (pm2 start app.js -i max) does this automatically. Each worker is an independent process with its own memory; state cannot be shared in-process (use Redis).
Memory leaks: common causes are event listeners not removed (EventEmitter.on inside a request handler that fires on each request; fix: EventEmitter.once or removeListener), closures holding large objects in scope longer than necessary, and global caches that grow without eviction.
Streams: use streams for large data (files, CSV, HTTP response bodies) instead of reading entirely into memory. pipe(readable, writable) connects them. pipeline() from stream/promises handles backpressure and cleanup automatically.
Frequently asked questions
Explore more