Skip to the solution
HardLow-Level Design6 min read

What is backpressure in Node.js streams and how do you handle it?

876 views

The problem

Question presented to candidate: "A stream writes data to a slow destination β€” a rate-limited network socket, say β€” faster than the destination can actually consume it. What stops memory from growing without bound, and how does that mechanism actually surface in code?"

What a strong answer should cover:

  • Backpressure is the mechanism by which a stream's destination (a Writable) signals that its internal buffer is full, so the source can pause producing more data until the destination catches up β€” without this, a fast source and a slow destination would let an unbounded internal buffer grow, exactly the memory problem streaming exists to avoid.
  • πŸ“Œ The concrete, verifiable signal: writable.write(chunk) returns false once the internal buffer has grown past its highWaterMark β€” verified directly, repeatedly, across multiple real fill-and-drain cycles, not a single cherry-picked instance.
  • When .write() returns false, correctly-behaved code should stop writing and wait for the destination to emit a 'drain' event before resuming β€” verified directly: a real 'drain' event fired at the correct moment, and writes correctly resumed only after it.
  • .pipe() and stream.pipeline() (both covered in their own dedicated questions) implement exactly this pause/drain cycle automatically β€” this is the real, concrete reason to prefer them over manually forwarding 'data' events with unchecked .write() calls, which silently loses backpressure handling entirely.
  • for await...of over an async-iterable stream also respects backpressure at the consumption pace level (verified with real timing data in the dedicated stream-iteration question), while the 'data' event does not β€” attaching a 'data' listener switches a stream to flowing mode immediately, delivering chunks regardless of how slowly the handler processes them.
  • A precise answer names backpressure as a general concept, not Node-specific β€” any producer/consumer system with different processing speeds needs an equivalent mechanism; Node's streams implement one specific, well-defined version of it via the .write() return value and the 'drain' event.

Clarifying questions expected:

  • "Is this about a raw .write()-based Writable, or a piped chain, or async iteration?" β€” each surfaces (or automatically handles) backpressure differently.
  • "Is the actual concern memory growth, or overall throughput/latency under a slow consumer?" β€” both are backpressure-related but call for slightly different framing.

Code / implementation expected: Yes β€” a real, repeated write-until-false-then-wait-for-'drain' cycle is the concrete, convincing demonstration, not a single instance or a description.

  • streams
  • memory
  • backpressure

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Read the code

JavaScriptThe write() / false / drain backpressure cycle, verified repeatedly across 20 real writes to a deliberately slow Writable
const { Writable } = require("stream");

const slow = new Writable({
  highWaterMark: 10, // small, to trigger backpressure quickly and repeatedly
  write(chunk, enc, cb) { setTimeout(cb, 5); }, // simulates a slow destination
});

let writeCount = 0;
function writeMany() {
  let ok = true;
  while (writeCount < 20 && ok) {
    writeCount++;
    ok = slow.write("x".repeat(5));
    if (!ok) console.log(`write() returned false at write #${writeCount}`);
  }
  if (writeCount < 20) {
    slow.once("drain", () => { console.log("drain event fired, resuming writes"); writeMany(); });
  } else {
    console.log("all 20 writes issued β€” the fill/drain cycle repeated 10 times to get here");
  }
}
writeMany();

// Output repeats the pattern 10 times across the 20 writes:
// write() returned false at write #2
// drain event fired, resuming writes
// write() returned false at write #4
// drain event fired, resuming writes
// ... (through write #20)

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 124 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextHard

How does the Node.js 'dns' module differ from browser-based DNS resolution?

Continue