Blocking vs Non-Blocking Code in Node.js

Introduction
When building backend applications with Node.js, one of the most important concepts to understand is blocking vs non-blocking code. This directly affects how fast your server responds to users.
Node.js is designed to be non-blocking, which allows it to handle many requests efficiently. But if blocking code is used incorrectly, it can slow down the entire server.
In this article, we will understand what blocking and non-blocking code mean, how they affect performance, and how Node.js handles asynchronous operations.
Main Content
What blocking code means
Blocking code is code that stops the execution of other operations until it finishes.
Example:
const fs = require("fs");
console.log("Start");
const data = fs.readFileSync("file.txt", "utf-8");
console.log(data);
console.log("End");
Output:
Start
(file content)
End
Here:
The program waits until the file is completely read
No other code runs during this time
This is called blocking because it blocks the execution.
What non-blocking code means
Non-blocking code allows the program to continue executing other tasks without waiting.
Example:
const fs = require("fs");
console.log("Start");
fs.readFile("file.txt", "utf-8", (err, data) => {
console.log(data);
});
console.log("End");
Output:
Start
End
(file content)
Here:
File reading happens in the background
The program continues execution
The result is handled later
Blocking vs non-blocking analogy
Think of waiting in a queue:
Blocking: You stand in line and wait until your work is done before doing anything else
Non-blocking: You submit your request and continue doing other tasks while waiting
Node.js uses the second approach.
Why blocking slows servers
Node.js runs on a single thread. If one request uses blocking code:
The entire server waits
Other users must wait
Performance decreases
Example:
If 100 users request data and one request uses blocking code:
- All other 99 users are delayed
This is why blocking code is dangerous in Node.js servers.
Async operations in Node.js
Node.js provides asynchronous APIs for most operations:
File system (
fs.readFile)Network requests
Database calls
These operations are handled in the background and do not block the main thread.
Real-world example: File reading
Blocking version:
const data = fs.readFileSync("data.txt", "utf-8");
console.log(data);
Non-blocking version:
fs.readFile("data.txt", "utf-8", (err, data) => {
console.log(data);
});
The non-blocking version is preferred in server applications.
Real-world example: Database calls
Blocking approach:
Wait for database response
Server cannot handle other requests
Non-blocking approach:
Send query
Continue handling other users
Process result when ready
This improves performance and scalability.
Blocking execution timeline
In blocking, tasks are executed one after another, causing delays.
Non-blocking execution timeline
In non-blocking, tasks run in the background and responses are handled later.
Key differences
| Feature | Blocking | Non-Blocking |
|---|---|---|
| Execution | Waits for task | Continues execution |
| Performance | Slower | Faster |
| Server handling | One request at a time | Multiple requests |
| Usage | Simple scripts | Real-world servers |
Practice Assignment
Try this example:
const fs = require("fs");
console.log("Start");
fs.readFile("file.txt", "utf-8", (err, data) => {
console.log("File read complete");
});
console.log("End");
Observe the output order and understand how non-blocking works.
Conclusion
Blocking code stops execution and can slow down a Node.js server, while non-blocking code allows multiple tasks to run efficiently. Since Node.js uses a single thread, avoiding blocking operations is essential for performance.
We learned how blocking and non-blocking code differ, how Node.js handles asynchronous operations, and why non-blocking code is preferred in real-world applications.
Understanding this concept will help you build faster and more scalable backend systems using Node.js.






