Skip to main content

Command Palette

Search for a command to run...

Blocking vs Non-Blocking Code in Node.js

Updated
4 min read
Blocking vs Non-Blocking Code in Node.js

Introduction:

Imagine you’re at a tea stall:

  • Blocking style → The shopkeeper serves only one customer at a time. Everyone else must wait.

  • Non-blocking style → The shopkeeper takes multiple orders quickly and prepares them as resources become available.

Node.js works like the second case: it doesn't stop everything for one task.

In this blog, we’ll dive into why Node.js remains a top choice for high-performance applications by mastering the fundamental shift from blocking execution to a non-blocking, event-driven architecture.

What Blocking Code Means

Blocking occurs when the execution of additional JavaScript in the Node.js process must wait until a non-JavaScript operation completes. This happens because Node.js is single-threaded. When you run a heavy synchronous task, you "block" the Event Loop.

Blocking code refers to operations that halt the execution of further code until the current task completes.

  • Impact: If a file takes 5 seconds to read, your entire server is "dead" for those 5 seconds. No other users can even load the homepage.

Example: Synchronous File Read

const fs = require('fs');

// The code stops here until the file is fully read
const data = fs.readFileSync('/file.md'); 
console.log(data);

console.log("This won't run until the file is finished!");

What Happens:

  • Node.js waits until the file is completely read

  • No other request can be handled during this time

What Non-Blocking Code Means

Non-blocking code allows the execution of the next lines of code without waiting for the current operation to finish. These are usually Asynchronous operations that are offloaded to the system kernel or a thread pool.

Non-blocking code allows execution to continue without waiting for a task to finish.

  • Impact: The server stays responsive. It initiates a task, moves on, and handles the result whenever it's ready.

Example: Asynchronous File Read

const fs = require('fs');

// Node starts this task and immediately moves to the next line
fs.readFile('/file.md', (err, data) => {
    if (err) throw err;
    console.log("File content ready!");
});

console.log("I run immediately, even before the file is read!");

What Happens:

  • File reading happens in the background

  • Execution continues instantly

  • Callback runs when file is ready

Why blocking slows servers

When a server uses blocking code:

  • Each request waits in line

  • CPU stays idle while waiting for I/O

  • Throughput decreases

Node.js uses a single thread to handle all requests. To visualize why blocking is a "server killer," look at this timeline:

Diagram: Blocking vs. Non-Blocking

Blocking Scenario (The Traffic Jam)

Request A (DB Query) |████████████████████| -> Done
Request B            |                     | (Waiting...)
Request C            |                     | (Waiting...)
                      ^ All other users are stuck until A finishes.

Non-Blocking Scenario (The Fast Lane)

Request A (DB Query) |█ (Offloaded)        | ... -> Callback Triggered
Request B (Static)   |██| -> Done
Request C (Static)   |██| -> Done
                      ^ Server keeps working while A is processed in background.

Async Operations in Node.js

Most "heavy lifting" in Node.js is non-blocking by default. These include:

  • I/O Operations: Reading/Writing to files or the network.

  • Database Calls: Querying MongoDB, PostgreSQL, etc.

  • Timers: setTimeout or setInterval.

When these operations are called, Node.js sends the task to Libuv (a C++ library), which handles the task in the background. Once finished, it pushes a callback onto the Task Queue, and the Event Loop eventually picks it up.

Example (Async with setTimeout)

console.log("Start");

setTimeout(() => {
  console.log("Async Task Done");
}, 2000);

console.log("End");

Output:

Start
End
Async Task Done

Shows non-blocking behavior clearly

Real-World Examples

1. File Reading

  • readFileSync() → Blocking ❌

  • readFile() → Non-blocking ✅

2. Database Call

db.getUser(id, (user) => {
  console.log(user);
});

While DB fetch happens:

  • Server can handle other users

3. API Request

fetch("https://api.example.com/data")
  .then(res => res.json())
  .then(data => console.log(data));

Network delay ≠ server freeze

Quick Comparison: Blocking vs Non-Blocking

Feature Blocking Non-Blocking
Execution Synchronous Asynchronous
Wait behavior Stops execution Continues execution
Performance Slow under load Scalable
Node.js usage Rare Preferred

Key Takeaways

  • Node.js is single-threaded but non-blocking

  • Blocking code freezes the event loop

  • Non-blocking uses callbacks, promises, async/await

  • Best performance comes from avoiding synchronous I/O

  • Always prefer async APIs in production systems

In closing

I hope that you’ve found this blog on “Blocking vs Non-Blocking Code in Node.js” helpful...!

That's all for today! 😁 You reached the end of the article 😍.

Want more..?

I write articles on princekumar-engineer.hashnode.dev, and also post development-related content on the following platforms:

Node.js

Part 4 of 15

A complete beginner-to-advanced guide to learning Node.js and Express.js, covering core concepts, asynchronous programming, REST APIs, middleware, file handling, and authentication techniques.

Up next

How Node.js Handles Multiple Requests with a Single Thread

Introduction: Imagine a busy restaurant with one highly efficient chef . Customers (requests) keep coming in The chef doesn’t cook one full dish before taking the next order Instead, he takes order