What is backpressure, and how do streams handle it?

3 minadvancednodejsbackpressurestreamspipehighwatermark

Quick Answer

Backpressure is when a fast Readable produces data quicker than a slow Writable can consume it. `write()` returns false when the internal buffer (highWaterMark) is full; you should pause until a 'drain' event. `pipe()` and `pipeline()` handle this automatically, which is why they're preferred over manual data/write loops.

Detailed Answer

Answer: Backpressure occurs when a data source outpaces the destination — e.g., reading a file from a fast SSD and writing to a slow network socket. Without handling it, data piles up in memory and can exhaust it.

How Writable streams signal it:

  • writable.write(chunk) returns false when the internal buffer has exceeded its highWaterMark (default 16 KB for byte streams).
  • When you get false, you should stop writing and wait for the 'drain' event before resuming.

Manual handling (illustrative):

readable.on('data', (chunk) => {
  const ok = writable.write(chunk);
  if (!ok) {
    readable.pause();                 // stop reading
    writable.once('drain', () => readable.resume()); // resume when drained
  }
});

The right way — let Node manage it:

const { pipeline } = require('stream/promises');

await pipeline(
  fs.createReadStream('huge.log'),
  zlib.createGzip(),
  fs.createWriteStream('huge.log.gz')
);
// pipeline handles backpressure AND propagates errors + cleans up
  • pipe() and pipeline() automatically pause/resume the source based on the destination's readiness.
  • pipeline() additionally forwards errors and destroys all streams on failure (avoiding leaks) — prefer it over pipe() for anything beyond trivial cases.

Interview point: backpressure is the reason to use pipe/pipeline instead of hand-rolling on('data') + write(); manual loops without drain handling leak memory under load.