Backpressure
Why It Matters
Backpressure is what prevents a fast source from overwhelming a slow destination. Without it, a Node.js process can consume too much memory, slow down, or crash while buffering data that cannot be written yet.
Streams are valuable because they include backpressure mechanisms. Good stream code lets those mechanisms work.
Core Concepts
When you call writable.write(chunk), it returns a boolean:
truemeans the writable can accept more data now.falsemeans its internal buffer is full enough that you should wait fordrain.
import { once } from 'node:events';
import { createWriteStream } from 'node:fs';
const writable = createWriteStream('numbers.txt');
for (let i = 0; i < 1_000_000; i += 1) {
const canContinue = writable.write(`${i}\n`);
if (!canContinue) {
await once(writable, 'drain');
}
}
writable.end();Ignoring the return value can create unbounded buffering.
Syntax and Examples
pipe handles backpressure
import { createReadStream, createWriteStream } from 'node:fs';
createReadStream('large-input.txt').pipe(createWriteStream('large-output.txt'));pipe() pauses and resumes the readable stream based on writable pressure.
pipeline is safer
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
await pipeline(
createReadStream('large-input.txt'),
createWriteStream('large-output.txt'),
);pipeline() adds better error and cleanup behavior.
Use Cases
Backpressure matters in:
- Large file copies
- HTTP downloads to slow clients
- Upload forwarding
- Database exports
- Log processing
- Message queue consumers
It is less visible with small files but becomes critical under load.
Common Mistakes
- Manually writing chunks and ignoring
write()returningfalse. - Collecting all chunks into an array before processing.
- Using
dataevent handlers that do async work without pausing or limiting. - Treating memory growth as unavoidable instead of a flow-control bug.
- Starting too many independent streams at once.
Practical Challenge
Write a script that writes one million lines to a file. First ignore the write() return value. Then update it to wait for drain. Compare memory usage with process.memoryUsage().rss.
Recap
Backpressure is flow control. Let pipe() or pipeline() manage it when possible. When writing manually, respect write() returning false and wait for drain.