Harith Zahid

Node.js - Sync, Async & Non-blocking I/O

In Node.js, these concepts are related but distinct:

Sync vs Async refers to your JavaScript code execution:

  • Synchronous: Code executes line by line, blocking the next line until the current one finishes
  • Asynchronous: Code initiates an operation and continues executing, with results handled later via callbacks, promises, or async/await

Blocking vs Non-blocking I/O refers to how the underlying system handles I/O operations:

  • Blocking I/O: The thread waits (is blocked) until the I/O operation completes
  • Non-blocking I/O: The thread continues working while the OS handles I/O in the background

Here's the key relationship in Node.js:

Node.js uses non-blocking I/O at the system level, but exposes both sync and async APIs to you. When you use async APIs (like fs.readFile), your code is async AND the I/O is non-blocking. When you use sync APIs (like fs.readFileSync), your code is sync BUT Node.js still uses blocking I/O underneath, which freezes the entire event loop.

// Async code + non-blocking I/O (good for servers)
fs.readFile('file.txt', (err, data) => {
  console.log('File read');
});
console.log('This runs immediately');

// Sync code + blocking I/O (blocks event loop)
const data = fs.readFileSync('file.txt');
console.log('This waits for file to be read');

The magic is that Node.js delegates I/O to the OS (via libuv), allowing a single thread to handle thousands of concurrent operations. Async APIs let you write code that takes advantage of this non-blocking I/O, while sync APIs force blocking behavior and should generally be avoided in server code.