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.