Skip to content

Node.js Blocking vs Non-Blocking — Complete Guide to Event Loop Performance

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Node.js Blocking vs Non. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js blocking operations halt the event loop until completion, while non-blocking operations delegate work and return immediately through callbacks, promises, or async-await.

What You'll Learn

By the end of this tutorial, you'll identify blocking and non-blocking APIs, measure event loop lag, convert blocking patterns to non-blocking, and optimize I/O-heavy applications.

Why Blocking Matters

Each blocking call freezes all concurrent requests. A single synchronous file read of 100ms delays every other user. Non-blocking design is essential for Node.js performance.

Real-World Use

An Express server with 1000 concurrent users must avoid sync file reads, JSON.parse on large payloads, and CPU-bound loops. Offloading these to worker threads or async APIs keeps responses fast.

Blocking Learning Path

flowchart LR
  A[Event Loop] --> B[Blocking vs Non-Blocking]
  B --> C[Async Patterns]
  C --> D[Error Handling]
  D --> E[Streams]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Synchronous Blocking Example

Synchronous methods like readFileSync block the event loop until the file is fully read. No other JavaScript executes during this time.

const fs = require("node:fs");
console.log("Start blocking read");
const data = fs.readFileSync("/etc/hostname", "utf8");
console.log("Data:", data.trim());
console.log("End blocking read");
// Other requests are queued during this entire period

Asynchronous Non-Blocking Example

Asynchronous methods like readFile return immediately. The callback runs when the result is ready, allowing other code to execute in the meantime.

const fs = require("node:fs");
console.log("Start non-blocking read");
fs.readFile("/etc/hostname", "utf8", (err, data) => {
  if (err) throw err;
  console.log("Data:", data.trim());
});
console.log("End non-blocking read");
// Output:
// Start non-blocking read
// End non-blocking read
// Data: <hostname>

Measuring Event Loop Lag

Use the blocking event loop lag to detect performance issues. Node.js provides Process.hrtime or perf_hooks for measurement.

const { performance, PerformanceObserver } = require("node:perf_hooks");
const obs = new PerformanceObserver((items) => {
  items.getEntries().forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms`);
  });
});
obs.observe({ type: "measure" });
performance.mark("start-blocking");
const data = require("node:fs").readFileSync(__filename, "utf8");
performance.mark("end-blocking");
performance.measure("Blocking File Read", "start-blocking", "end-blocking");

Converting Blocking to Non-Blocking Patterns

Replace sync methods with async alternatives. For CPU-heavy operations, use worker threads or split work across chunks.

const fs = require("node:fs");
const { promisify } = require("node:util");
const readFileAsync = promisify(fs.readFile);
async function processFiles(files) {
  const results = await Promise.all(
    files.map((file) => readFileAsync(file, "utf8").catch(() => null))
  );
  return results.filter(Boolean);
}
processFiles(["file1.txt", "file2.txt"]).then(console.log);

Common Mistakes

1. Using JSON.parse on Large Payloads Synchronously

JSON.parse blocks the event loop. For large payloads, stream the data and parse incrementally or use worker threads.

2. Blocking with Every Synchronous Method

Many developers assume fs.readFileSync is fine for small files. Multiple concurrent small reads add up to significant blocking.

3. Not Using Streams for Large Data

Loading entire files into memory with readFileSync or readFile blocks both event loop and memory. Use streams for large files.

4. CPU-Intensive Loops on the Main Thread

Array operations, cryptography, and data transformation on large datasets block the event loop. Use worker threads.

5. Assuming async-await Is Always Non-Blocking

Promise.all does not make blocking operations non-blocking. Only the event loop can be blocked by synchronous code inside async functions.

Practice Questions

1. What happens to the event loop during a blocking call?

The event loop stops processing entirely. No timers, I/O, or other callbacks execute until the blocking call completes.

2. How do you convert readFileSync to non-blocking?

Use fs.readFile with a callback or fs.promises.readFile with async-await.

3. What is the best way to handle CPU-bound tasks in Node.js?

Use worker threads (Worker) to offload CPU work without blocking the event loop.

4. How can you measure event loop lag?

Use process.hrtime.bigint() or performance.now() before and after known sync operations, or use the perf_hooks module.

5. Challenge: Write a benchmark that shows blocking vs non-blocking latency with concurrent operations.

const http = require("node:http");
let blocking = false;
const server = http.createServer((req, res) => {
  if (blocking) {
    require("node:fs").readFileSync("/etc/hostname");
  }
  res.end("ok");
});
server.listen(3000);

FAQ

Is async-await always non-blocking?

No. Async-await does not make synchronous code non-blocking. Only the event loop and libuv provide non-blocking I/O.

Why does Node.js have synchronous APIs at all?

For convenience in scripts, startup code, and CLI tools where blocking is acceptable. Avoid in production servers.

Does Promise.all execute in parallel?

No. Promise.all runs promises concurrently but not parallel. They still share the main thread.

Can blocking be acceptable in some cases?

Yes. During server startup, configuration loading, or CLI scripts. Never in request handlers.

How do streams help with blocking?

Streams process data in chunks, allowing the event loop to handle other requests between chunks.

Mini Project: Event Loop Lag Monitor

Build a real-time event loop lag monitoring utility.

const { performance } = require("node:perf_hooks");
function monitorLag(intervalMs = 1000) {
  function check() {
    const before = performance.now();
    setImmediate(() => {
      const lag = performance.now() - before;
      if (lag > 50) {
        console.warn(`Event loop lag detected: ${lag.toFixed(2)}ms`);
      }
      setTimeout(check, intervalMs);
    });
  }
  check();
}
monitorLag(500);

What's Next

Node.js Async Patterns Node.js Error Handling Node.js Streams

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro