skip to content

Blocking Queues

BlockingQueue is the backbone of producer-consumer: put and take block, offer and poll can time out, and the implementation you pick decides your buffering and backpressure. SynchronousQueue with its zero capacity is the one interviewers most often ask you to explain.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is a BlockingQueue and what problem does it solve compared to a regular queue?

level: juniorimportance: must knowfreq 78%

answer

  1. Thread-safe queue + blocking on empty/full
  2. put/take block, offer/poll don't (timed variants too)
  3. Bounded = backpressure, no OOM
  4. No null elements (null = 'nothing available')
  5. Work queue behind ThreadPoolExecutor

basics

~20 s

A BlockingQueue is a thread-safe queue where a thread that takes from an empty queue waits until an item arrives, and (if bounded) a thread that adds to a full queue waits until space frees up. It makes producer-consumer hand-off safe and simple.

solid answer

~40 s

BlockingQueue is a thread-safe queue in java.util.concurrent designed for producer-consumer coordination. Beyond ordinary thread safety, it adds blocking: take() waits when the queue is empty until an element appears, and put() waits when a bounded queue is full until space frees up. This lets producers and consumers hand off work without writing manual wait/notify, locks, or busy-polling loops, and a bounded queue provides backpressure so fast producers can't exhaust memory. Implementations include ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, DelayQueue and PriorityBlockingQueue. It also offers non-blocking offer/poll, timed offer/poll, and is the work queue behind ThreadPoolExecutor. The interface forbids null elements, since null is used as a sentinel for 'nothing available'.

go deeper

for a junior

Knows it's a thread-safe queue that blocks on empty (take) and full (put), used for producer-consumer.

for a middle

Can name the four operation families (throws/special-value/blocks/timed) and explain backpressure from bounded capacity and the no-null rule.

for a senior

Connects it to ThreadPoolExecutor's work queue, reasons about choosing bounded vs unbounded for memory safety, and picks the right operation (put vs timed offer) per failure policy.

for a principal

Frames blocking queues within end-to-end backpressure/flow-control design, weighs them against reactive streams or lock-free alternatives, and sets capacity/rejection policy as a system-stability decision.

## The problem Imagine one or more **producer** threads creating work items and one or more **consumer** threads processing them. They need a shared buffer to pass items through. A plain `java.util.Queue` (like `ArrayDeque`) is **not thread-safe**: if two threads modify it at once you get corrupted state. Even a synchronized queue solves only *safety*, not *coordination*: what should a consumer do when the queue is empty? Spinning in a loop checking `isEmpty()` (busy-waiting) wastes CPU; rolling your own `wait()`/`notify()` is error-prone. ## What a BlockingQueue adds `BlockingQueue<E>` (in `java.util.concurrent`) is a `Queue` that is **thread-safe** AND **blocking**: - **`take()`** removes and returns the head; if the queue is **empty**, the calling thread *blocks* (sleeps efficiently) until an element becomes available. - **`put(e)`** inserts an element; if the queue is **full** (only relevant for bounded queues), the calling thread *blocks* until space frees up. 'Blocks' means the thread is parked by the JVM/OS and consumes no CPU until it's signaled — far better than busy-waiting. ## Four families of operations For each of insert and remove, BlockingQueue offers four behaviors when the operation can't proceed immediately: | | Throws exception | Returns special value | Blocks forever | Blocks with timeout | |---|---|---|---|---| | **Insert** | `add(e)` | `offer(e)` → boolean | `put(e)` | `offer(e, time, unit)` → boolean | | **Remove** | `remove()` | `poll()` → null if empty | `take()` | `poll(time, unit)` → null on timeout | | **Examine** | `element()` | `peek()` | — | — | - `put`/`take` are the *blocking* pair — the workhorses of producer-consumer. - `offer`/`poll` are *non-blocking*: they return immediately with a boolean / null instead of waiting. - The *timed* `offer`/`poll` wait up to a bound, then give up — useful to avoid hanging forever. ## Why bounded queues matter: backpressure A **bounded** queue has a fixed capacity. When it fills, producers calling `put` block. This is **backpressure**: a fast producer is throttled to the speed of consumers, so the queue can't grow without limit and exhaust memory. An **unbounded** queue (or an unbounded use of one) never blocks producers, which risks an `OutOfMemoryError` if producers outrun consumers. ## No null elements BlockingQueue **forbids `null`**. `poll()` returns `null` to mean 'nothing was available', so a stored `null` would be ambiguous. Adding `null` throws `NullPointerException`. ## Where it's used The most common real use is as the work queue inside a `ThreadPoolExecutor`: submitted tasks go into a BlockingQueue, and worker threads `take()` from it. It is the standard building block for pipelines and thread pools, so you rarely need manual locks for hand-off.

  • Why does BlockingQueue prohibit null elements?
    Because poll() returns null to signal 'queue empty / nothing available'. Allowing a stored null would make that return value ambiguous, so adding null throws NullPointerException.
  • What's the difference between put and offer?
    put blocks until space is available (or forever); offer returns immediately with false if it can't insert (or after a timeout in the timed variant).

saying these in an interview costs you the question

  • Claiming BlockingQueue busy-waits/spins — it parks the thread efficiently
  • Saying you can store null elements
  • Thinking every BlockingQueue is bounded (LinkedBlockingQueue defaults unbounded)
  • Confusing offer (non-blocking) with put (blocking)

context

open as a page

Compare ArrayBlockingQueue and LinkedBlockingQueue. When would you choose each?

level: middleimportance: must knowfreq 72%

basics

~20 s

ArrayBlockingQueue is backed by a fixed-size array and is always bounded. LinkedBlockingQueue uses linked nodes and is optionally bounded (unbounded by default). ArrayBlockingQueue uses one lock; LinkedBlockingQueue uses two (put and take), so it often has higher throughput under contention.

open as a page

Given the put/take/offer/poll family, how do you choose the right operation for a producer-consumer and handle interruption and timeouts?

level: middleimportance: must knowfreq 60%

basics

~20 s

Use put/take when blocking forever is acceptable, offer/poll when you must act immediately and not wait, and the timed offer/poll when you'll wait but only up to a limit. put and take throw InterruptedException, so handle interruption (usually by restoring the interrupt flag and stopping).

open as a page

How do DelayQueue and PriorityBlockingQueue differ from a plain FIFO BlockingQueue?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Neither is FIFO. PriorityBlockingQueue returns elements in priority order (a heap), and is unbounded. DelayQueue holds Delayed elements that only become available for take() after their delay expires; until then the queue acts empty even if it has elements.

open as a page

What is a SynchronousQueue and when is it the right choice?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A SynchronousQueue has zero capacity: it holds no elements. Each put must wait for a matching take (and vice versa) — it's a direct hand-off between two threads. Use it when you want producers and consumers to rendezvous with no buffering.

open as a page