Blocking vs Non-Blocking Code in Node.js

When building backend applications, one of the most important concepts to understand is how your server handles multiple requests.
Should it wait for one task to finish?
Or should it move forward and handle other tasks?
This is where blocking and non-blocking code come into play.
What is Blocking Code?
Blocking code means the program waits for a task to complete before moving to the next one.
The execution is paused until the operation finishes.
Example:
const fs = require('fs');
const data = fs.readFileSync('file.txt', 'utf-8');
console.log(data);
console.log("Next line");
Here:
The server waits until the file is fully read
Only then it moves to the next line
What is Non-Blocking Code?
Non-blocking code allows the program to start a task and move on without waiting.
The result is handled later using callbacks, promises, or async/await.
Example:
const fs = require('fs');
fs.readFile('file.txt', 'utf-8', (err, data) => {
console.log(data);
});
console.log("Next line");
Here:
File reading starts
Program continues execution immediately
Result comes later
Waiting vs Continuing
Imagine you're cooking:
❌ Blocking:
You put water to boil
You stand there and wait
Do nothing else
Non-Blocking:
You put water to boil
You start cutting vegetables
Come back when water is ready
Node.js follows the second approach.
Why Blocking Slows Down Servers
In a server:
Multiple users send requests at the same time
If one request blocks the server → others must wait
Result:
Slow response time
Poor user experience
Reduced scalability
Example Scenario:
How Node.js Handles Async Operations
Node.js uses:
Non-blocking I/O
Event loop
Flow:
This allows Node.js to handle thousands of requests efficiently.
Real-World Example: File Handling
❌ Blocking Version
const fs = require('fs');
app.get('/data', (req, res) => {
const data = fs.readFileSync('bigfile.txt', 'utf-8');
res.send(data);
});
Problem:
Server waits for file to load
Other requests get blocked
Non-Blocking Version
const fs = require('fs');
app.get('/data', (req, res) => {
fs.readFile('bigfile.txt', 'utf-8', (err, data) => {
res.send(data);
});
});
Benefit:
Server continues handling other requests
Faster overall performance
Real-World Example: Database Call
❌ Blocking Thinking (Bad Approach)
Wait for DB response
Freeze execution
Non-Blocking (Actual Node.js Style)
app.get('/users', async (req, res) => {
const users = await db.getUsers(); // async
res.json(users);
});
While waiting:
Node.js handles other requests
No blocking happens
Quick Comparison
| Feature | Blocking Code | Non-Blocking Code |
|---|---|---|
| Execution | Waits | Continues |
| Performance | Slow | Fast |
| Scalability | Poor | High |
| Use Case | Rare | Preferred in Node.js |
Conclusion
Understanding blocking vs non-blocking code is essential for writing efficient Node.js applications.
By using non-blocking operations, you can build servers that are fast, scalable, and capable of handling multiple users at the same time.




