Backpressure in the Web Streams API is a demand signal that keeps a fast producer from continually outrunning a slower consumer. It does not discard data or make work faster; it gives upstream code a reason to pause before queues grow. This matters whenever a response or file is processed in chunks instead of being accumulated in memory first.

desiredSize reports queue room

A WritableStream writer’s desiredSize is the strategy’s high-water mark minus the size currently queued. A positive value means the queue is below that threshold; zero or a negative value is a signal to stop producing and wait. It is a demand indicator, not a promise about total process memory or proof that the consumer has finished a chunk. The meaning of “size” depends on the queuing strategy, so a queue of several chunks may still contain very large objects.

Wait in the producer loop

When writing a producer loop directly, wait for writer.ready before preparing more work, then await writer.write(chunk) before advancing when the application needs serialized production. If a producer ignores these waits and starts unlimited asynchronous tasks, work can accumulate outside the stream’s own queue. Bound concurrent reads, transforms, and writes as well. Backpressure does not automatically free a large buffer that the application already created.

A fulfilled writer.ready means the queue has room for more writes under its strategy; it does not mean downstream business work has finished. The promise returned by writer.write() tracks processing of that chunk by the stream’s writable side, not a database commit or a remote service’s completion. If the application needs those stronger guarantees, wait for its own acknowledgement. Also account for every producer sharing a writer: individually well-behaved loops can still exceed the intended concurrency when their combined work is unbounded.

Pipes and transforms pass demand upstream

ReadableStream.pipeTo(WritableStream) connects demand along the pipe and also has defined error and cancellation behavior. With a TransformStream in the middle, a fast writable side can be slowed when the readable side is not being consumed quickly enough. Inspect every stage in a longer chain: a custom queue or detached task that keeps accepting work can grow even while a downstream stream is correctly applying backpressure.

A high-water mark is not a memory cap

The default CountQueuingStrategy counts chunks, so a 1 KB chunk and a 20 MB chunk can each count as one. For an ordinary ReadableStream, a custom size function can measure chunks in bytes or another useful unit. A byte stream created with type: "bytes" already counts chunks in bytes; supplying strategy.size causes construction to throw a RangeError. Set its highWaterMark directly and do not pass a separate ByteLengthQueuingStrategy. One oversized chunk, or production work created outside the queue, can still require more memory than the threshold. Bound maximum chunk size and concurrent work separately.

Expert perspective: propagate pressure across every boundary

MDN contributors describe backpressure as demand flowing upstream through a pipe chain. Google Chrome Developers’ implementation guidance uses desiredSize and writer.ready as wait signals while calling attention to chunk sizes and queue management. The practical interpretation is to inspect every custom buffer and cancellation path between producer, transform, and consumer instead of setting a high-water mark on one stream and assuming memory is bounded. The API does not promise a process-wide memory ceiling.

Sources