Chapter 46 โ The Event Loop
๐ Definition
The event loop is Node.js's mechanism for performing non-blocking I/O on a single-threaded JavaScript engine by offloading work to the OS or thread pool and handling completion callbacks in phases.
๐ Explanation
Node.js runs JS on a single main thread. It achieves concurrency by:
- Using OS-level async I/O (epoll/kqueue/IOCP).
- Using a thread pool (libuv) for fs, dns, crypto.
- Polling completed work in a loop and running JS callbacks one at a time.
The Six Phases (simplified)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโ>โ timers โ โ setTimeout / setInterval callbacks
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โ โ pending callbacks โ โ deferred TCP errors, etc.
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โ โ idle, prepare โ โ internal
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โ โ poll โ โ retrieve new I/O; run their callbacks
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โ โ check โ โ setImmediate callbacks
โ โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โโโโค close callbacks โ โ socket.on('close'), etc.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโBetween every phase, the microtask queue drains: process.nextTick callbacks first, then resolved Promise callbacks.
๐ป Code Example โ Microtask vs Macrotask
console.log("1");
setTimeout(() => console.log("2"), 0); // macrotask (timers phase)
setImmediate(() => console.log("3")); // macrotask (check phase)
Promise.resolve().then(() => console.log("4")); // microtask
process.nextTick(() => console.log("5")); // microtask (highest priority)
console.log("6");
// Output: 1, 6, 5, 4, 2, 3
// (2 vs 3 ordering can vary โ depends on the I/O context.)๐ป Code Example โ Blocking the Loop
const http = require("http");
http.createServer((req, res) => {
if (req.url === "/slow") {
// โ CPU-bound loop blocks every other request
let sum = 0;
for (let i = 0; i < 1e10; i++) sum += i;
return res.end(`Sum=${sum}`);
}
res.end("OK");
}).listen(3000);
// While /slow runs, /OK requests pile up and wait.Fix 1 โ Offload to a Worker Thread
const { Worker } = require("worker_threads");
http.createServer((req, res) => {
if (req.url === "/slow") {
const worker = new Worker(`
const { parentPort } = require("worker_threads");
let sum = 0;
for (let i = 0; i < 1e10; i++) sum += i;
parentPort.postMessage(sum);
`, { eval: true });
worker.on("message", (sum) => res.end(`Sum=${sum}`));
}
});Fix 2 โ Break the work into chunks
function expensiveAsync(callback) {
let i = 0, sum = 0;
function step() {
const end = i + 1e6;
for (; i < end && i < 1e10; i++) sum += i;
if (i < 1e10) setImmediate(step);
else callback(sum);
}
step();
}๐ป Code Example โ setImmediate vs setTimeout(0)
setImmediate(() => console.log("immediate"));
setTimeout(() => console.log("timeout"), 0);
// Inside an I/O callback, setImmediate runs first.
// At top level, order is non-deterministic.๐ป Code Example โ process.nextTick Caution
process.nextTick(function recurse() {
process.nextTick(recurse); // โ starves I/O
});
// nextTick queue drains completely before moving on โ abuse = frozen server.๐ป Code Example โ Async I/O is Non-Blocking
const fs = require("fs/promises");
console.log("start");
fs.readFile("big.txt", "utf8").then(() => console.log("done"));
console.log("end");
// Output: start, end, done
// Reading happens off the main thread.๐ป Code Example โ Thread Pool Limit
// crypto.pbkdf2 uses the libuv thread pool (default 4 workers)
const crypto = require("crypto");
for (let i = 0; i < 8; i++) {
console.time(`hash-${i}`);
crypto.pbkdf2("pw", "salt", 100000, 64, "sha512", () => {
console.timeEnd(`hash-${i}`);
});
}
// First 4 finish around the same time; next 4 wait.
// Increase with: UV_THREADPOOL_SIZE=8 node app.js๐ Real-World Impact
- Servers handle thousands of concurrent connections by avoiding sync work.
- Identify CPU-bound endpoints with profilers and move them to workers/queues.
- Tune
UV_THREADPOOL_SIZEfor crypto-heavy workloads.
๐ฏ Likely Interview Questions
- Explain the event loop.
- Why is Node.js single-threaded but still scalable?
- Difference between
setImmediate,setTimeout(0), andprocess.nextTick? - What happens if you put a
while(true)in a handler? โ Blocks all other requests. - What is the libuv thread pool? โ A pool of OS threads used for fs, crypto, dns, zlib. Default size 4.
โ Middleware, Testing & Alternatives | Index | Next: async/await โ