We use analytics cookies.We use cookies to analyze site traffic and improve your experience.
Loading...
3 system design questions lean on this idea. Each walks through the full answer.
System Design Masterclass · Reliability Resilience
Free preview: the first 2 parts of this 7-part lesson.
Already bought the course? Sign in to read the whole lesson.
this lesson is part of
System Design Masterclass
Unlock this lesson and the 751 other paid lessons
$25 once, by PayPal
See the System Design courseread the rest of this lesson in
System Design Masterclass
Unlock this lesson and the 751 other paid lessons
$25 once, by PayPal
See the System Design coursein Reliability Resilience
This lesson is part of the full System Design Masterclass. Unlock it along with 769 other lessons for a one-time payment.
One-time payment by card or PayPal
Get Lifetime AccessOne-time payment: UPI, card or netbanking
Get Lifetime Access18 Foundation lessons are free, no payment needed. Try them now
It is easy and good
“It is really nice and good Please join the course”
A producer can publish a million messages per second. A downstream Java consumer can process a hundred thousand. What happens to the other 900,000?
Without , they pile up in an unbounded queue inside the consumer's JVM. The heap fills. Garbage collection starts running every few seconds, then every few hundred milliseconds. Each GC pause blocks all processing, so the consumer falls further behind. Eventually the JVM throws an OutOfMemoryError and the entire service dies. Every message in that queue is lost.
LinkedIn engineers hit this exact failure mode around 2013 while scaling their event processing pipelines. Their fix became one of the foundational contributions to the Reactive Streams specification in 2014. The core insight was simple: instead of letting the producer push data at whatever rate it wants, let the consumer pull demand. The consumer says "I can handle 100 more items." The producer sends exactly 100. The buffer cannot overflow because the consumer controls the faucet.
This idea is not new. has used flow control since 1981, with the receive window telling the sender exactly how many bytes the receiver can accept. Backpressure for application-level services applies the same principle one layer higher: let the slowest stage in the pipeline set the pace for the entire system.
When a consumer cannot keep up with a producer, there are exactly three things you can do. Every implementation picks one or combines them.
| Strategy | What Happens | When to Use | Risk |
|---|---|---|---|
| Block the producer | Producer waits until consumer catches up | Synchronous pipelines where data loss is unacceptable | Producer thread pool exhaustion |
| Buffer with a bound | Items queue up to a limit, then block or reject | Async pipelines with bursty traffic | Buffer itself adds |
| Drop items | Newest or oldest items are discarded | Real-time metrics, sensor data, video frames | Data loss, which may be unacceptable |
The critical word is "bound." An unbounded buffer is not a backpressure strategy. It is a delayed crash. The buffer grows until it consumes all available memory, at which point the system dies in exactly the same way it would have without the buffer, just later.
Reactive Streams, the specification LinkedIn helped create, uses the pull model. The consumer calls request(n) to ask for n items. The producer never sends more than requested. Project Reactor, RxJava, Akka Streams, and Java's Flow API all implement this contract. The pattern appears anywhere a fast source feeds a slow sink: Kafka consumer groups, database result set streaming, HTTP/2 flow control, and streaming RPCs.