Stop 02 · Node.js FundamentalsTopics · 4/18

Non-Blocking I/O

Part of the Backend Developer Roadmap

In short

Reading a file or querying a database takes forever in CPU time. Node doesn't stand and wait: it starts the job, serves other users, and comes back when the result is ready.

Node's approach of starting an I/O operation (file read, network call) and continuing to execute other code while it completes in the background, instead of waiting.

The mascot waiter passes an order slip through the kitchen window while turning to the next table.

Think of it like this

The one-waiter dhaba

One waiter runs the whole dhaba. He takes table 1's order, passes the slip to the kitchen, and goes straight to table 2 instead of standing by the stove. When a dish is ready, the kitchen calls out and he serves it. One waiter, many happy tables.

Watch it happen

Two users, one thread

User A
User B
Node
Disk

GET /report

1/6User A asks for a report that needs a large file from disk.

The big ideas

What you need to know

I/O is mostly waiting

Disk reads, database queries and API calls spend most of their time waiting on something outside your code.

Sync calls block everyone

readFileSync and friends make the whole process wait. Fine at startup, harmful inside request handlers.

libuv does the waiting

Network I/O uses the OS's own async features. File work goes to a small thread pool, four threads by default.

Great for I/O, not for CPU

Node shines at many slow-ish I/O tasks. Heavy maths or image processing needs worker threads or another service.

See it in code

Walk through the code

js
1import http from "node:http";2import { readFileSync } from "node:fs";3import { readFile } from "node:fs/promises";4 5http.createServer(async (req, res) => {6  if (req.url === "/sync") {7    const data = readFileSync("big.csv"); // every other user waits8    return res.end(`read ${data.length} bytes\n`);9  }10  if (req.url === "/async") {11    const data = await readFile("big.csv"); // others keep being served12    return res.end(`read ${data.length} bytes\n`);13  }14  res.end("ok\n");15}).listen(3000);

1/3readFileSync freezes the thread until the whole file is read. Every other request waits in line.

Create a big.csv of a few hundred MB first. Hit /sync and, at the same time, hit / from a second terminal. Repeat with /async and compare.

Save yourself the pain

Common mistakes

Oops

Using fs.readFileSync or other *Sync calls inside request handlers.

Do this instead

Sync calls are fine at startup, like loading config. Inside handlers, use the promise versions from node:fs/promises.

Oops

Thinking non-blocking means Node runs your JavaScript in parallel.

Do this instead

Your JavaScript still runs on one thread. Only the waiting, for disk or network, happens in the background.

Your mission

Feel the block

~20 min

Quick check

A Node server calls await db.query() and the query takes 200 ms. What is the main thread doing during those 200 ms?

Go deeper

  1. 1Compare a blocking `readFileSync` call to a non-blocking `readFile` callback version.
  2. 2Learn how libuv's thread pool handles I/O operations behind the scenes.
  3. 3Understand why this model lets Node handle high concurrency with a single thread.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge