When Blocking Is OK — and When It Isn't
Sync vs Async fs
Sync fs methods block the event loop. Use them in scripts and at startup, never in a request handler.
What you'll learn
- Recognize the *Sync suffix
- Know when blocking is fine
- Avoid blocking the event loop
fs.readFileSync(path) returns the contents immediately. But while
it’s reading, nothing else in your process happens — the event
loop is paused.
Sync API
import { readFileSync, writeFileSync } from "node:fs";
const text = readFileSync("config.json", "utf8");
const data = JSON.parse(text);
writeFileSync("processed.json", JSON.stringify(data)); Simple, but blocking.
The Cost
Imagine a web server. A request comes in. Your handler calls
readFileSync("big.csv"). While it reads — could be milliseconds,
could be seconds — no other requests are processed. Every
client sits waiting.
The async equivalent yields control back to the event loop. Other requests get serviced concurrently.
When Sync Is Fine
- CLI scripts that run once and exit
- Build tools that read configs on startup
- Server startup code — load config, env, certs before binding the port
// server.mjs — startup code, runs once
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("config.json", "utf8"));
// server bound below; sync was OK because we hadn't started serving
http.createServer((req, res) => {
// ...async from here on
}).listen(config.port); When Sync Is Wrong
- Inside request handlers
- Inside loops that handle many items
- Anywhere latency matters
A Concrete Test
// blocks for ~3 seconds — try it
const start = Date.now();
readFileSync("very-big-file.bin");
console.log(`Took ${Date.now() - start}ms`); If you ran this in a request handler under load, every concurrent request waits 3 seconds. Bad.
The Rule of Thumb
In server code: never *Sync. In a one-off script: knock
yourself out.