Producer Consumer
Let producers put items on a shared queue and consumers take them off at their own pace, so a bounded buffer absorbs bursts and decouples how fast work arrives from how fast it gets handled.
The problem
Baker.bakeAndDeliver(driver) hands a fresh loaf straight to a driver and waits until the
driver is ready to take it. That works for one loaf at a time, but the moment production
outpaces delivery - a dozen loaves finishing while one driver is still on the road - there is
nowhere for the extra loaves to go. The baker is stuck waiting on the driver's schedule,
which has nothing to do with baking.
The two activities - producing and consuming - are running at genuinely different, often unpredictable, paces. Wiring them directly together means the slower one always sets the speed for both.
The solution
Put a bounded buffer between them. Producers add items to the queue and move on; consumers remove items from the queue whenever they are ready. Neither side calls the other directly, and neither side needs to know how many of the other kind exist, or how fast they are running.
The bound matters as much as the queue itself: a full queue makes the producer wait (backpressure), and an empty queue makes the consumer wait, cheaply, instead of spinning. The buffer is what turns "these two things run at different speeds" from a bug into a fact the system was designed to handle.
- 1The baker works at their own pace, entirely unaware of whether a driver is nearby or three towns away.
- 2The finished loaf goes on the rack. The baker never hands it directly to a driver.
- 3When the driver is ready for the next load, they take a loaf off the rack - blocking cheaply if it happens to be empty.
- 4A dozen loaves finish in a rush. The rack absorbs the burst; the baker never has to wait for a driver to catch up.
- 5The rack has room for ten loaves, not twelve. Once full, the baker has to wait before setting down the eleventh.
- 6The driver clears space by taking loaves at their own pace, which is what lets the baker resume.
- 7With space freed, production resumes exactly where it left off - neither side ever spoke to the other directly.
Structure
Neither Baker nor DeliveryDriver has any relationship to the other in this diagram - both
point only at BoundedQueue. That missing direct line is the whole point.
Code
Same example three ways: a baker and a delivery driver decoupled by a bounded rack.
When to use it
- Work is produced and consumed at genuinely different, often bursty rates, and wiring the two together directly means one side's schedule dictates the other's.
- More than one producer or consumer may exist (or may need to exist later), and neither side should have to know how many of the other kind are currently running.
Pitfalls
- Unbounded queues. Removing the size limit removes backpressure entirely - a slow consumer no longer causes the producer to wait, it causes memory to grow until something else breaks.
- Losing items on shutdown. A queue that still holds items when the process exits needs an explicit drain-or-discard decision; silently losing in-flight items is rarely the right default for anything that matters.
- Starvation from unfair queues. A queue implementation that lets some producers or consumers monopolize access can starve others; most real blocking queues guarantee FIFO fairness for exactly this reason.
Don't confuse it with
- Thread Pool. A thread pool is usually Producer-Consumer with the consumer side constrained to a fixed, bounded set of worker threads; Producer-Consumer itself makes no claim about how many producers or consumers there are, or that consumers must be threads at all.
- Observer. Observer's
notify()is a synchronous push to every subscriber the instant it fires. Producer-Consumer's queue is what lets the consumer run on a completely different schedule than the producer - that decoupling in time is the entire reason the buffer exists. - Message queues / brokers (Kafka, SQS). Same idea, grown up: durable, often distributed, frequently with multiple independent consumer groups. The in-process version here is the same relationship at a smaller scale.
Check yourself
What does a bounded queue do when a producer tries to add an item while it is full?