The idea: the extra items must go somewhere

A producer makes 3 items per tick. A consumer can handle only 1. Every tick, 2 items are left over. They cannot vanish: they pile up in memory, they make the producer wait, they are thrown away, or they are never made. Backpressure is the choice among these: a way for the slow side to push back on the fast side.

A producer making 3 items per tick feeds a queue in front of a consumer that handles 1 item per tick. The 2 extra items per tick can grow an unbounded queue until an OOM kill, block the producer in put(), be dropped, or never be produced because the consumer sends request(n) demand upstream
The producer is 3 times faster than the consumer; each strategy sends the surplus to a different place.

The page runs one scenario on a tick clock. Pick a strategy and press Next Tick, or run a demo. The counters show items produced, consumed and dropped. The memory panel charges 10 MB per queued item against a 120 MB limit. The last counter is the latency of the item at the head of the queue: how many ticks it has waited.

Unbounded queues fail late

The easy fix is a queue with no limit. Every put() succeeds, so everything looks fine at first. But the queue grows by 2 items per tick, and so does memory. The head item waits longer each tick, so the results the consumer delivers get staler and staler. At tick 6 the queue holds 13 items, 130 MB, over the 120 MB limit. The kernel's OOM killer ends the process (exit code 137), and all 13 queued items are lost (Demo 1: unbounded queue, OOM-killed). An unbounded queue does not remove the problem. It only delays it, then turns it into a crash.

Bounded queue: block the producer

Give the queue a capacity, here 5. When it is full, put() blocks: the producer's thread stops, holding its item, until the consumer frees a slot. Java's ArrayBlockingQueue and Go's buffered channels work this way. The slowdown travels upstream by itself: the producer now makes 1 item per tick, the consumer's rate (Demo 2: 11 produced, 6 consumed, 0 dropped, memory never above 50 MB). The cost is a blocked thread, and a blocked thread can't do anything else, such as answer a health check or read from a socket.

Bounded queue: drop newest or drop oldest

Sometimes the producer can't wait: a sensor, a mouse, a market feed. Then a full queue must drop something. Drop newest throws away the arriving item and keeps the old ones. Drop oldest throws away the head and keeps the new one. Both drop 8 of 18 items in 6 ticks (Demos 3 and 4). What differs is which items survive.

Queue contents at the end of tick 6. Drop newest: #7, #10, #13, #16, and the head #7 has waited 3 ticks. Drop oldest: #15, #16, #17, #18, and the head #15 has waited 1 tick. Both dropped 8 items
Same load, same 8 drops: drop newest keeps old items (the head waited 3 ticks), drop oldest keeps fresh ones (1 tick).
drop newestdrop oldest
a full queue dropsthe arriving itemthe item at the head
head waited at t = 63 ticks1 tick
good forwork that must be done in order (jobs, log lines)state where only the latest matters (prices, positions, UI events)
examplesReactor onBackpressureDrop, a full TCP accept backlogReactor onBackpressureLatest, Akka Streams OverflowStrategy.dropHead

Pull: Reactive Streams request(n)

Instead of pushing items and handling the overflow, let the consumer say how many it wants. In Reactive Streams the subscriber calls request(n) on its subscription, and the publisher may send at most n items with onNext. This demand flows upstream, the opposite way to the data. On this page the consumer calls request(5) when it subscribes (room for 5 items) and request(1) each time it finishes one.

Time-down sequence between a publisher and a subscriber: onSubscribe, request(5), three onNext calls in tick 1 leaving demand 2, request(1) after the first item, three onNext calls in tick 2 leaving demand 0, then one onNext per tick after each request(1)
Demand goes up, items come down: the publisher never sends more than was requested, so the buffer never overflows.

Queued items plus open demand never exceed 5, so nothing is dropped and memory stays bounded. When demand is 0, the producer simply makes nothing. No thread is blocked, so the same signal works across async code and across a network (Demo 5: 10 produced, 6 consumed, 0 dropped). The numbers look like blocking because both settle at the consumer's rate; the difference is how the signal travels.

Bursts: what a bounded queue is for

A bounded queue is not only about overload. Real traffic comes in bursts with quiet time in between. A queue of 5 takes a burst of 6 items in 2 ticks without blocking or dropping anything, and the consumer drains it during the idle ticks after (Demo 6: 6 produced, 6 consumed, 0 dropped). So size the queue for the bursts you expect, not for sustained overload: if the producer is faster on average, any queue fills, and only blocking, dropping or demand helps.

In real systems

systembackpressure
TCPthe receiver's window says how many bytes it can take: pull, at the byte level
Kafkaconsumers pull with poll(), so a slow consumer just falls behind; the log is a bounded-by-retention buffer
Java ThreadPoolExecutora bounded work queue, then a rejection policy (drop, or CallerRunsPolicy to slow the caller)
Project Reactor, RxJava, Akka Streamsrequest(n) demand, plus onBackpressureBuffer, Drop and Latest when a source can't be slowed
HTTP serversa full accept queue or worker pool: refuse new connections, or answer 503 or 429

See also Bounded Buffer / Producer–Consumer for how a blocking queue is built from semaphores, Rate Limiting: Token Bucket vs Leaky Bucket for limiting a client's rate before the queue, How Kafka Moves a Message for a pull-based consumer, and How TCP Congestion Control Works for a sender that slows down to what the path can carry.

See also Push vs Pull Messaging: the same slow-consumer problem between a broker and its consumer, where a missing prefetch limit gets the consumer OOM-killed and a pull consumer just builds up lag.

What the page leaves out

Real time instead of ticks; consumers whose speed varies; batching demand (Reactor's limitRate asks again only when 75% of the prefetch is used); timeouts on a blocked put() (offer(item, timeout)); load shedding by priority; propagating backpressure through many stages, where one slow stage slows them all; and garbage-collector thrashing, which usually makes a growing queue slow long before the OOM kill.