Explain flowing vs paused mode for readable streams. How do you consume a stream?

3 minadvancednodejsstreamsflowingpausedasync-iterator

Quick Answer

A Readable stream is in paused mode by default; you pull data with .read() or switch to flowing mode by attaching a 'data' listener or calling .pipe(), where chunks are pushed automatically. Modern code often consumes streams with for-await-of, which handles flow and backpressure cleanly.

Detailed Answer

Answer: A Readable stream operates in one of two modes governing how data moves.

Paused mode (default): you explicitly pull data:

readable.on('readable', () => {
  let chunk;
  while ((chunk = readable.read()) !== null) {
    process(chunk);
  }
});

Flowing mode: data is pushed to you as fast as it arrives. You enter it by:

  • attaching a 'data' listener,
  • calling .pipe(), or
  • calling .resume().
readable.on('data', chunk => process(chunk)); // now flowing

Switching: adding a 'data' handler or pipe() → flowing; .pause() → paused; removing pipes/handlers can pause again.

Modern, preferred approach — async iteration:

async function readAll(readable) {
  let total = 0;
  for await (const chunk of readable) {   // handles flow + backpressure
    total += chunk.length;
  }
  return total;
}

for await...of is the cleanest way to consume a stream: it respects backpressure, propagates errors as exceptions (usable with try/catch), and reads until the stream ends.

Gotcha: In flowing mode, if you attach a 'data' listener but the consumer is slow and you don't manage backpressure, memory can grow. pipe/pipeline/for await avoid this; a bare 'data' loop does not.