The Event Loop and Non-Blocking I/O
The Event Loop and Non-Blocking I/O
Node uses a single main thread with an event loop underneath. Understanding this model explains why Node handles many connections with little memory.
Blocking vs. non-blocking
A blocking operation like reading a file synchronously stops every other request while it finishes. Non-blocking versions start the work and continue, running a callback when done:
// Blocking
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
// Non-blocking
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data);
});
How the event loop works
The event loop is a cycle that watches several queues. It runs timers, then pending I/O callbacks, then setImmediate callbacks, then closes, repeating forever while the process lives. When an operation finishes, its callback is queued and picked up in a later tick.
What that means for your code
Long synchronous work starves the loop, so a heavy loop in one request blocks everything else. Offload with async APIs, worker threads, or child processes. Prefer the callback and promise versions of fs and network functions.
Promises fit the loop
await lets you write async code that reads like sync code while staying non-blocking:
async function load() {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
}
The event loop keeps accepting new work while file reads run in the background thread pool.
Key Points
- Node runs JavaScript on a single main thread.
- Non-blocking I/O keeps the server responsive.
- The event loop cycles through callback queues.
- Sync versions of file and network calls block others.
- Use async, await, and promises for clean non-blocking code.