skip to content

What do the block and timeout arguments of queue.Queue.get() and put() control?

level: juniorimportance: must knowfreq 60%

answer

  1. Two arguments, three call shapes
  2. Waiting is what you get by default
  3. The impatient forms raise, never return
  4. Two exception classes in that module
  5. One of them needs a maxsize

basics

~20 s

By default both wait forever: get waits for an item, put waits for room on a bounded queue. Passing block=False raises queue.Empty or queue.Full at once; passing a timeout waits that many seconds and then raises the same exception.

solid answer

~40 s

`queue.Queue.get()` and `put()` both default to `block=True, timeout=None`, which parks the calling thread until the queue can serve it. `block=False` turns the call into a poll that either succeeds now or raises `queue.Empty` / `queue.Full` immediately — which is exactly what `get_nowait()` and `put_nowait()` do. A `timeout` gives a bounded wait and raises the same exception when the deadline passes. Note that `put()` can only block or raise `Full` on a queue created with a positive `maxsize`; an unbounded `queue.Queue()` is never full. The non-blocking forms raise rather than returning a sentinel, so `None` is a perfectly valid item. Prefer a timeout over an infinite wait in any thread you need to be able to stop, and treat `qsize()`, `empty()` and `full()` as advisory — another thread can invalidate them before you act.

code

python · 16 lines
python
import queue

q = queue.Queue(maxsize=1)
q.put("frame-0")

try:
    q.put("frame-1", timeout=0.05)      # blocks, then gives up
except queue.Full:
    print("producer pushed back")

print(q.get(timeout=0.05))

try:
    q.get_nowait()                      # block=False, no waiting
except queue.Empty:
    print("nothing to consume")

go deeper

for a junior

Be ready to name the two exceptions, queue.Empty and queue.Full, and to say that the non-blocking and timed calls raise them instead of returning a value. Know that get_nowait() is just get(block=False).

for a middle

Explain the mechanics: a bounded queue is what makes put() able to block at all, blocked waiters sit on a condition variable and cost no CPU, and qsize() is a snapshot that a concurrent thread can invalidate before you use it.

for a senior

Show that you use timeouts as an operational tool. CPython cannot kill a thread, so an untimed get() is an unstoppable worker; demonstrate the timeout-plus-stop-flag loop, or Queue.shutdown() on 3.13+, and explain what you log when a consumer keeps timing out.

for a principal

Own the convention for the codebase: whether workers may ever block indefinitely, what the standard shutdown handshake is, and how queue waits show up in metrics so a stalled consumer is visible before customers notice it.

## One method, three call shapes `queue.Queue` is the standard library's blocking channel between threads. Its two workhorse methods share a signature shape: `put(item, block=True, timeout=None)` and `get(block=True, timeout=None)`. Those two arguments give you three usable call shapes, and interviewers ask about them because each one fails differently. 1. **`q.get()` / `q.put(x)`** — the default. The calling thread waits indefinitely until the queue can serve it. 2. **`q.get(block=False)` / `q.get_nowait()`** — a poll. It either succeeds right now or raises. 3. **`q.get(timeout=2.0)`** — a bounded wait. It succeeds within two seconds or raises. The single most important detail is that shapes 2 and 3 **raise** rather than returning a sentinel. `queue.Empty` and `queue.Full` are exception classes defined in the `queue` module, and there is no `None` return to test for — which is deliberate, because `None` is a perfectly legitimate item to put on a queue. ```python import queue q = queue.Queue(maxsize=1) q.put("a") try: q.put("b", timeout=0.05) except queue.Full: ... ``` ## What blocking actually costs Internally a `Queue` holds a `collections.deque` guarded by a `threading.Lock` plus two condition variables, one for "not empty" and one for "not full". A blocked `get()` releases the lock and waits to be notified, so it burns no CPU and — because a thread waiting on a condition variable has released the GIL — it does not slow the threads that are running. Blocked consumers are cheap; that is why the default is to wait rather than to spin on `qsize()`. ## Full only exists if you asked for it `put()` can only block, or raise `queue.Full`, on a queue constructed with a positive `maxsize`. `queue.Queue()` and `queue.Queue(maxsize=0)` are unbounded, and `put()` on them always returns immediately. A candidate who says "put blocks when the queue is full" without that qualifier has usually never bounded one. It matters, because an unbounded queue silently converts a producer that outruns its consumers into unbounded memory growth rather than into a visible wait. ## Timeouts are a shutdown tool, not a nicety CPython has no API to kill a thread. A worker parked forever in `q.get()` cannot be interrupted, so a process that wants to exit cleanly is stuck waiting for an item that may never arrive. The classic fix is a bounded wait inside a loop that also checks a stop flag: ```python while not stopping.is_set(): try: item = q.get(timeout=0.5) except queue.Empty: continue ``` Since Python 3.13 there is a first-class alternative: `queue.Queue.shutdown()` wakes every blocked `get()` and `put()` by raising `queue.ShutDown`, so consumers exit without a per-worker sentinel and without polling. On 3.14 that is the idiom to reach for when you control both ends. ## Empty does not mean finished A timed `get()` that raises `queue.Empty` means only "nothing arrived within the timeout". It says nothing about whether producers are done — a slow producer can put an item a millisecond later. Treating `Empty` as end-of-stream is one of the most common real bugs in threaded pipelines, and it is why completion is signalled explicitly: `task_done()` and `join()`, an explicit sentinel value, or `shutdown()`. ## The advisory methods `qsize()`, `empty()` and `full()` return a value that was true at the moment they were called and may already be false when they return, because another thread runs concurrently. `if not q.empty(): q.get()` is a check-then-act race; the correct pattern is to call `get_nowait()` and catch `queue.Empty`. `qsize()` is fine for a metric or a log line, never for control flow. ## Edge details worth knowing * A negative `timeout` raises `ValueError: 'timeout' must be a non-negative number`; `timeout=0` is legal and behaves like the non-blocking form. * `get_nowait()` and `put_nowait()` are literally defined as `get(block=False)` and `put(item, block=False)` — no separate mechanism. * `queue.SimpleQueue.put()` accepts `block` and `timeout` for signature compatibility and ignores them: it is unbounded and never blocks. * Catch the specific exception. `except Exception` around a timed `get()` swallows real errors from the surrounding code and turns a bug into an idle loop. ## Choosing among the three shapes A rule of thumb that survives review: use the plain blocking call inside a worker that lives for as long as the process and is torn down by `shutdown()` or a sentinel; use a timeout in any thread that must periodically do something else — check a stop flag, refresh a lease, emit a heartbeat — because the timeout *is* how that thread gets a chance to run; and use the non-blocking form only when you genuinely have useful work to do instead of waiting, since a `get_nowait()` in a bare `while True` loop is a spin that burns a core and, on CPython, contends for the GIL against the very threads you want to make progress. On the producer side the same logic applies in reverse. A blocking `put()` on a bounded queue is a feature — it is what makes a fast producer wait for its consumers. A `put(item, timeout=...)` that catches `queue.Full` is how you convert that wait into an explicit decision: drop the item, spill it, or report the system as busy. Which of those is right is a design question, but making it visible starts with passing a timeout instead of waiting forever.

  • If a timed queue.Queue.get() raises Empty, does that mean the producers have finished?
    No. `queue.Empty` from a timed `get()` means only that nothing arrived before the deadline; a producer can put an item immediately afterwards. Completion has to be signalled explicitly — by `task_done()` plus `join()`, by an explicit sentinel item per consumer, or by `Queue.shutdown()` on 3.13+. Inferring end-of-stream from an empty queue silently drops work whenever a producer stalls for longer than the timeout.
  • Why is `if not q.empty(): q.get()` the wrong way to avoid blocking on a queue.Queue?
    It is a check-then-act race. `empty()` reports a snapshot; between that call and the `get()`, another consumer can take the only item, and your `get()` then blocks forever on what you believed was a non-blocking path. The same applies to `qsize()` and `full()`. Ask the queue for the item and handle failure: `get_nowait()` inside `try/except queue.Empty`. Reserve `qsize()` for metrics.
  • What does queue.Queue.put() do when the queue was created without a maxsize?
    It returns immediately, always. `queue.Queue()` and `queue.Queue(maxsize=0)` are unbounded, so the not-full condition is never false, `block` and `timeout` never come into play, and `queue.Full` can never be raised. That is convenient and dangerous: a producer faster than its consumers has nothing to slow it down, and the backlog grows as process memory until something else fails.

saying these in an interview costs you the question

  • Thinks get() returns None when the queue is empty
  • Says put() blocks even without a maxsize
  • Treats a timed Empty as 'all producers finished'
  • Calls empty() or qsize() to decide whether to get()
  • Catches bare Exception instead of queue.Empty
  • Confuses the queue.Empty class with the empty() method

context