Difficulty: Intermediate
How does the Node.js event loop work? How is it different from the browser event loop?
Node.js uses a single-threaded event loop powered by libuv for non-blocking I/O. While the main thread is single, I/O operations (file, network, DNS) are offloaded to libuv's thread pool or OS async mechanisms.
Node.js event loop phases: 1. Timers: setTimeout, setInterval callbacks 2. Pending callbacks: deferred I/O callbacks 3. Idle/Prepare: internal use 4. Poll: retrieve new I/O events, execute I/O callbacks 5. Check: setImmediate callbacks 6. Close callbacks: socket.on('close')
Microtasks (process.nextTick, Promise callbacks) run between each phase.
console.log('1: sync');
setTimeout(() => console.log('2: setTimeout'), 0);
setImmediate(() => console.log('3: setImmediate'));
process.nextTick(() => console.log('4: nextTick'));
Promise.resolve().then(() => console.log('5: promise'));
console.log('6: sync');
Sync first, then nextTick (highest priority microtask), then Promise microtask, then timer macrotask, then setImmediate (check phase).
const express = require('express');
const app = express();
// BAD: blocks the event loop for ALL users
app.get('/hash', (req, res) => {
const crypto = require('crypto');
// This takes ~500ms and blocks everything
const hash = crypto.pbkdf2Sync(
'password', 'salt', 100000, 64, 'sha512'
);
res.json({ hash: hash.toString('hex') });
});
// GOOD: non-blocking with async version
app.get('/hash-async', async (req, res) => {
const crypto = require('crypto');
const hash = await new Promise((resolve, reject) => {
crypto.pbkdf2(
'password', 'salt', 100000, 64, 'sha512',
(err, key) => err ? reject(err) : resolve(key)
);
});
res.json({ hash: hash.toString('hex') });
});
Sync crypto blocks the event loop, making the server unresponsive. The async version offloads to the thread pool.
// main.js
const { Worker } = require('worker_threads');
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', {
workerData: data,
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
// worker.js
const { workerData, parentPort } = require('worker_threads');
// CPU-heavy computation runs in separate thread
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const result = fibonacci(workerData);
parentPort.postMessage(result);
Worker threads run JavaScript in parallel threads - ideal for CPU-intensive work without blocking the main event loop.
Event Loop, libuv, Non-blocking I/O, Single Thread, Worker Threads