Event Loop in Node.js
What is the Event Loop?
The Event Loop is the mechanism that allows Node.js to perform non-blocking I/O operations despite JavaScript being single-threaded. It continuously checks for and executes callbacks from the event queue.
Event Loop Phases
┌───────────────────────────┐
┌─>│ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘ ┌───────────────┐
│ ┌─────────────┴─────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └─────────────┬─────────────┘ │ data, etc. │
│ ┌─────────────┴─────────────┐ └───────────────┘
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘1. Timers Phase
Executes callbacks scheduled by setTimeout() and setInterval().
setTimeout(() => {
console.log('Timeout callback');
}, 1000);
setInterval(() => {
console.log('Interval callback');
}, 2000);2. Pending Callbacks Phase
Executes I/O callbacks deferred to the next loop iteration.
3. Poll Phase
Retrieves new I/O events and executes I/O related callbacks.
const fs = require('fs');
fs.readFile('file.txt', (err, data) => {
console.log('File read complete'); // Poll phase
});4. Check Phase
Executes setImmediate() callbacks.
setImmediate(() => {
console.log('Immediate callback');
});5. Close Callbacks Phase
Executes close event callbacks.
const server = require('http').createServer();
server.on('close', () => {
console.log('Server closed'); // Close callbacks phase
});Microtasks vs Macrotasks
Microtasks (Higher Priority)
// Promise callbacks
Promise.resolve().then(() => {
console.log('Promise 1');
});
// process.nextTick (highest priority)
process.nextTick(() => {
console.log('nextTick');
});
// Output:
// nextTick
// Promise 1Macrotasks
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
// Output order may varyExecution Order Example
console.log('1. Start');
setTimeout(() => {
console.log('2. setTimeout');
}, 0);
setImmediate(() => {
console.log('3. setImmediate');
});
Promise.resolve().then(() => {
console.log('4. Promise');
});
process.nextTick(() => {
console.log('5. nextTick');
});
console.log('6. End');
// Output:
// 1. Start
// 6. End
// 5. nextTick
// 4. Promise
// 2. setTimeout
// 3. setImmediateprocess.nextTick()
Executes callback after current operation completes, before event loop continues.
console.log('Start');
process.nextTick(() => {
console.log('nextTick 1');
process.nextTick(() => {
console.log('nextTick 2');
});
});
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
// Output:
// Start
// End
// nextTick 1
// nextTick 2
// PromisesetImmediate() vs setTimeout()
// In I/O cycle, setImmediate is always first
const fs = require('fs');
fs.readFile('file.txt', () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});
// Output:
// setImmediate
// setTimeoutBlocking the Event Loop
// BAD - Blocks event loop
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
app.get('/fib/:n', (req, res) => {
const result = fibonacci(req.params.n); // Blocks!
res.send(result.toString());
});
// GOOD - Non-blocking
app.get('/fib/:n', async (req, res) => {
const result = await calculateFibonacci(req.params.n);
res.send(result.toString());
});Best Practices
- Avoid blocking operations in the event loop
- Use async operations for I/O
- Break up CPU-intensive tasks using setImmediate
- Use worker threads for heavy computation
Interview Tips
- Explain event loop phases: Timers, poll, check, close
- Describe execution order: Microtasks before macrotasks
- Show nextTick vs setImmediate: Priority differences
- Discuss blocking: How to avoid blocking event loop
- Mention single-threaded: How Node handles concurrency
Summary
The Event Loop enables Node.js to perform non-blocking I/O operations by offloading operations to the system kernel. It processes callbacks in phases: timers, pending callbacks, poll, check, and close callbacks. Microtasks (process.nextTick, Promises) execute before macrotasks (setTimeout, setImmediate).
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.