IndexError: pop from empty list happens when you call list.pop() on an empty list. This is one of the most common Python bugs in queues, stacks, work-stealing loops, and item-by-item processors. Here are 4 patterns to handle it cleanly.

Minimal reproducer
queue = []
item = queue.pop() # IndexError: pop from empty list
Fix 1: Check length before popping
while queue:
item = queue.pop()
process(item)
# Or as a one-liner with conditional expression
item = queue.pop() if queue else None
The truthiness check if queue is the most Pythonic way to test if a list is non-empty. Same applies to dicts, sets, and strings.
Fix 2: try/except for race conditions
try:
item = queue.pop()
except IndexError:
item = None # or break, or sleep and retry
Use this pattern in multi-threaded code where another thread might pop between your length-check and your pop. The try/except is atomic; the check-then-pop is not.
Fix 3: collections.deque for safe popping with timeout
from collections import deque
queue = deque([1, 2, 3])
# Pop from left (FIFO) or right (LIFO)
while queue:
item = queue.popleft() # O(1), unlike list.pop(0) which is O(n)
process(item)
For producer-consumer patterns, switch to collections.deque (O(1) popleft) or queue.Queue (thread-safe with blocking get).
Fix 4: Sentinel default with no exception
SENTINEL = object()
def safe_pop(lst, default=SENTINEL):
return lst.pop() if lst else default
item = safe_pop(queue)
if item is SENTINEL:
print("Queue empty, nothing to do")
else:
process(item)
When to use each
| Scenario | Best pattern |
|---|---|
| Single-threaded loop | while queue: queue.pop() |
| Multi-threaded queue | queue.Queue with blocking get |
| FIFO from front | collections.deque.popleft() |
| Concurrent producer/consumer | asyncio.Queue or queue.Queue |
Frequently Asked Questions
What is the difference between list.pop() and list.pop(0)?
list.pop() removes the last item (O(1)). list.pop(0) removes the first item (O(n), because all remaining items shift left). For FIFO queues, use collections.deque.popleft() instead, which is O(1).
Why does my pop() fail intermittently in multi-threaded code?
Race condition. Thread A checks “if queue”, sees items, then Thread B pops the last item, then Thread A tries to pop and gets IndexError. Use try/except IndexError (atomic) or queue.Queue (thread-safe with blocking).
Is list.pop() equivalent to del list[-1]?
Almost. Both remove the last item. pop() returns it, del does not. Both raise IndexError on empty list. Use pop() when you need the value, del when you only need to remove.
How do I implement a thread-safe stack in Python?
For LIFO stacks, queue.LifoQueue is thread-safe with blocking put/get. For LIFO with non-blocking, wrap a list in a threading.Lock. For async code, use asyncio.LifoQueue.
Can I catch IndexError without try/except in modern Python?
For checking-not-handling, just use truthy test (if queue: …). For handling, contextlib.suppress(IndexError) is a one-line alternative to try/except pass. Python 3.10+ structural pattern matching can also handle it with case [first, *rest]: cases.
