Explain the Node.js event loop and its phases.
The event loop is libuv's loop that runs ready callbacks on the main thread. Each turn has six phases, each with its own queue:
- timers: due
setTimeoutandsetIntervalcallbacks - pending callbacks: some deferred system I/O callbacks
- idle, prepare: internal
- poll: wait for and run I/O callbacks
- check:
setImmediatecallbacks - close callbacks:
'close'events
After every callback (timers and
setImmediatetoo, since Node 11), Node drainsprocess.nextTick, then promise microtasks. See the event loop.- timers: due
In what order do
setTimeout(0),queueMicrotaskandPromise.thenrun?JavaScriptconst log = []; setTimeout(() => log.push('timeout'), 0); queueMicrotask(() => log.push('microtask')); Promise.resolve().then(() => log.push('promise')); log.push('sync'); setTimeout(() => console.log(log.join(' ')), 5);Sync code runs first, then microtasks in queueing order:
queueMicrotaskwas called before.then. The loop reaches the timers phase only when the microtask queue is empty, even for a0ms timer.What is the difference between
process.nextTick,setImmediateandsetTimeout(fn, 0)?process.nextTickruns right after the current operation, before promise callbacks (in CommonJS; see the ES module question).setImmediateruns in the check phase, after I/O polling.setTimeout(fn, 0)runs in the timers phase after at least 1 ms.// Node.js, CommonJS file setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); Promise.resolve().then(() => console.log('promise')); process.nextTick(() => console.log('nextTick')); console.log('sync');Node prints
sync,nextTick,promise, thentimeoutandimmediatein either order. The names are backwards:nextTickis the immediate one, and a recursivenextTickcan starve I/O.Does
setTimeout(fn, 0)orsetImmediaterun first?From the main module it is not guaranteed: it depends on whether 1 ms has passed when the loop reaches the timers phase. Inside an I/O callback
setImmediatealways runs first, because check comes right after poll.const fs = require('node:fs'); fs.readFile(__filename, () => { setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); }); // Node.js always prints: // immediate // timeoutsetImmediateis a Node.js API, not a web standard, so this order is a Node.js rule; see setImmediate vs setTimeout.Why does
process.nextTickrun after a promise callback in an ES module but before it in CommonJS?Because an ES module's top-level code runs inside a promise job. When a CommonJS file ends, Node drains nextTick first, then microtasks. An ES module body ends while microtasks are already draining, so its promise callbacks run first.
process.nextTick(() => console.log('nextTick')); Promise.resolve().then(() => console.log('promise')); console.log('sync');File Node.js prints order.cjssync,nextTick,promiseorder.mjssync,promise,nextTickThe process.nextTick docs show both orders. Never depend on this order; make it explicit with
await.What happens when you block the event loop?
Everything waits: timers fire late, requests queue up, health checks time out.
JavaScriptconst start = Date.now(); setTimeout(() => { const waited = Date.now() - start; console.log('asked for 10 ms, waited at least 200:', waited >= 200); }, 10); // 200 ms of synchronous work, like a huge JSON.parse or a sync crypto call while (Date.now() - start < 200) {} console.log('busy loop done');Common causes:
JSON.parseon huge payloads, syncfsorcryptoin a handler, a regex with catastrophic backtracking. Measure withperf_hooks.monitorEventLoopDelay(); fix by chunking withsetImmediateor a worker thread.What is the libuv thread pool, and which operations use it?
A pool of threads (4 by default) that libuv uses for work the OS cannot do asynchronously. Your JavaScript never runs there.
Uses the pool Does not fs(notfs.watch)sockets, HTTP dns.lookupdns.resolve*async crypto.pbkdf2,scrypttimers async zlibchild process pipes The trap: with 4 threads, 8 concurrent
pbkdf2calls take about twice as long as 4. Raise it with theUV_THREADPOOL_SIZEenvironment variable before Node starts (maximum 1024).How can one Node.js thread handle thousands of concurrent connections?
A waiting connection costs almost nothing: Node registers each socket with the OS (epoll, kqueue, IOCP) and runs code only when data arrives, where a thread-per-request server parks a thread per client. The limit is CPU per request: at 5 ms of JavaScript each, one process tops out near 200 requests per second.
Does
forEachwait for anasynccallback?JavaScriptconst sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function run() { const done = []; [30, 10, 20].forEach(async (ms) => { await sleep(ms); done.push(ms); }); const afterLoop = done.length; await sleep(50); console.log(afterLoop, done.join(',')); } run();forEachignores the promises its callback returns, so right after the loop nothing is done, and the sleeps finish in time order. Useawait Promise.all(items.map(...))(parallel) orfor...ofwithawait(one at a time). See async and await.How do you turn a callback API into a promise in Node.js?
Use
util.promisifyfor any function with an error-first callback, or the promise APIs Node ships:fs/promises,timers/promises,stream/promises.JavaScriptconst util = require('node:util'); function legacyDouble(x, callback) { setTimeout(() => { if (x < 0) return callback(new Error('negative input')); callback(null, x * 2); }, 1); } const double = util.promisify(legacyDouble); async function main() { console.log(await double(21)); try { await double(-1); } catch (err) { console.log('caught:', err.message); } } main();What is the difference between
Promise.all,Promise.allSettled,Promise.raceandPromise.any?They differ in when they settle:
Method Fulfills Rejects allall fulfill, with an array at the first rejection allSettledall settle, with a status each never racefirst to settle, if it fulfilled first to settle, if it rejected anyfirst to fulfill all reject ( AggregateError)JavaScriptconst ok = (ms, value) => new Promise((resolve) => setTimeout(() => resolve(value), ms)); const fail = (ms, msg) => new Promise((_, reject) => setTimeout(() => reject(new Error(msg)), ms)); async function main() { try { await Promise.all([ok(20, 'a'), fail(10, 'b failed'), ok(30, 'c')]); } catch (err) { console.log('all:', err.message); } const settled = await Promise.allSettled([ok(20, 'a'), fail(10, 'b failed')]); console.log('allSettled:', settled.map((r) => r.status).join(', ')); console.log('race:', await Promise.race([ok(20, 'slow'), ok(5, 'fast')])); console.log('any:', await Promise.any([fail(5, 'x'), ok(20, 'first success')])); } main();When
Promise.allrejects, the other operations keep running; nothing is cancelled.
Node.js Event Loop and Async Interview Questions
How Node.js runs timers, I/O callbacks and promises on one thread, and the ordering questions built on that.