The Event Loop
This is the most important concept in Node.js. Understanding the event loop explains why Node.js is fast and how to write efficient code.
The Problem: Blocking Code
Imagine a restaurant with one waiter:
Blocking approach:- Take order from Table 1
- Go to kitchen, wait until food is ready (10 minutes)
- Bring food to Table 1
- Now take order from Table 2...
Tables 2, 3, 4 wait forever. Terrible service.
Non-blocking approach:- Take order from Table 1, give to kitchen
- Take order from Table 2, give to kitchen
- Take order from Table 3, give to kitchen
- Food ready for Table 1? Deliver it.
- Food ready for Table 3? Deliver it.
Same one waiter, but efficient. This is Node.js.
Blocking Code Example
// blocking.js
const fs = require('fs');
console.log('1. Starting...');
// This BLOCKS - nothing else runs until file is read
const data = fs.readFileSync('large-file.txt', 'utf8');
console.log('2. File read, length:', data.length);
console.log('3. Done');
Output:
1. Starting...
(waits 2 seconds while reading file)
2. File read, length: 1000000
3. Done
If 1000 users hit your server during that 2-second wait, they all wait.
Non-Blocking Code Example
// non-blocking.js
const fs = require('fs');
console.log('1. Starting...');
// This is NON-BLOCKING - Node continues immediately
fs.readFile('large-file.txt', 'utf8', (err, data) => {
console.log('3. File read, length:', data.length);
});
console.log('2. This runs while file is being read!');
Output:
1. Starting...
2. This runs while file is being read!
(file finishes reading)
3. File read, length: 1000000
Notice: "2" prints before "3"! Node didn't wait.
How the Event Loop Works
┌───────────────────────────┐
┌─>│ timers │ <- setTimeout, setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ <- I/O callbacks
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ <- Retrieve new I/O events
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ <- setImmediate
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │
└───────────────────────────┘
Simplified version:
- Run your code
- Check if any async operations finished
- Run their callbacks
- Repeat until nothing left to do
See It In Action
// event-loop-demo.js
console.log('1. Synchronous - runs first');
setTimeout(() => {
console.log('4. setTimeout - runs after 0ms delay');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise - runs before setTimeout');
});
console.log('2. Synchronous - runs second');
Output:
1. Synchronous - runs first
2. Synchronous - runs second
3. Promise - runs before setTimeout
4. setTimeout - runs after 0ms delay
Even with 0ms delay, setTimeout runs after Promises. The event loop has priorities!
Real-World Impact: Server Example
Blocking server (BAD):// bad-server.js
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
// BLOCKS - while reading, no other requests are handled
const data = fs.readFileSync('big-file.txt', 'utf8');
res.end(data);
});
server.listen(3000);
If the file takes 100ms to read, you can only handle 10 requests/second.
Non-blocking server (GOOD):// good-server.js
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
// NON-BLOCKING - can handle other requests while reading
fs.readFile('big-file.txt', 'utf8', (err, data) => {
res.end(data);
});
});
server.listen(3000);
While one file is being read, Node handles other requests. Thousands of concurrent connections, one thread.
Common Blocking Operations to Avoid
// BAD - These block the event loop
fs.readFileSync()
fs.writeFileSync()
crypto.pbkdf2Sync()
JSON.parse(hugeString) // Can block if string is massive
while (condition) { } // Infinite or long loops
// GOOD - Non-blocking alternatives
fs.readFile()
fs.writeFile()
crypto.pbkdf2()
// For CPU-heavy tasks: use Worker Threads
Try It Yourself
Create this file to see blocking in action:
// blocking-test.js
const fs = require('fs');
function heavyComputation() {
// Simulate CPU-heavy work (this BLOCKS!)
const start = Date.now();
while (Date.now() - start < 2000) {
// Busy loop for 2 seconds
}
console.log('Heavy computation done');
}
console.log('Starting...');
setTimeout(() => {
console.log('Timeout callback (should run after 100ms)');
}, 100);
heavyComputation(); // This blocks the event loop!
console.log('After heavy computation');
Output:
Starting...
(waits 2 seconds - the setTimeout is BLOCKED!)
Heavy computation done
After heavy computation
Timeout callback (should run after 100ms)
The timeout was set for 100ms but actually ran after 2+ seconds because the heavy computation blocked everything.
Practice with Real Projects
Build and deploy Node.js applications on pre-configured cloud environments. No local setup headaches.
Key Takeaways
- Blocking code stops everything until it finishes
- Non-blocking code starts operations and continues immediately
- Event loop manages callbacks when async operations complete
- Always prefer async versions:
readFileoverreadFileSync - Single thread + event loop = thousands of concurrent connections
Next, let's work with files using the fs module!