Of course! Let's dive into the topic of queues in Python.

While Python has a built-in list type, it's not an efficient implementation of a queue. We'll start with why, and then explore the correct and standard ways to implement a queue in Python.
What is a Queue?
A queue is a fundamental data structure that follows the First-In, First-Out (FIFO) principle. Think of it like a real-world queue or a line at a checkout counter.
- Enqueue: The process of adding an element to the back (or "rear") of the queue.
- Dequeue: The process of removing an element from the front of the queue.
| Operation | Description | Time Complexity (Good) | Time Complexity (Bad) |
|---|---|---|---|
| Enqueue | Add an item to the end. | O(1) | O(n) |
| Dequeue | Remove an item from the front. | O(1) | O(n) |
| Peek/Front | Look at the front item without removing it. | O(1) | O(1) |
| Size | Get the number of items. | O(1) | O(1) |
| Is Empty | Check if the queue is empty. | O(1) | O(1) |
The "Wrong" Way: Using a Python list
You might be tempted to use a Python list as a queue.
# Using a list as a queue (not recommended)
my_queue = []
# Enqueue
my_queue.append('A') # Add to the end
my_queue.append('B')
my_queue.append('C')
print(f"Queue after enqueuing: {my_queue}") # Output: ['A', 'B', 'C']
# Dequeue
item = my_queue.pop(0) # Remove from the front (index 0)
print(f"Dequeued item: {item}") # Output: A
print(f"Queue after dequeuing: {my_queue}") # Output: ['B', 'C']
Why is this bad?

The pop(0) operation on a Python list has a time complexity of O(n). When you remove the first element, Python has to shift all the remaining elements one position to the left to fill the gap. For a very long queue, this becomes very inefficient.
The append() operation is efficient (O(1)), but since pop(0) is not, using a list as a queue is a performance anti-pattern.
The "Right" Way: The collections.deque
For general-purpose queue operations, the best and most Pythonic way is to use collections.deque. It's designed to have fast appends and pops from both ends.
A deque (pronounced "deck," short for "double-ended queue") is perfect for implementing a queue.
How to Use deque
from collections import deque
# Create a deque object
my_queue = deque()
# --- Enqueue (add to the right) ---
my_queue.append('A')
my_queue.append('B')
my_queue.append('C')
print(f"Queue after enqueuing: {my_queue}") # Output: deque(['A', 'B', 'C'])
# --- Dequeue (remove from the left) ---
item = my_queue.popleft()
print(f"Dequeued item: {item}") # Output: A
print(f"Queue after dequeuing: {my_queue}") # Output: deque(['B', 'C'])
# --- Peek at the front item ---
front_item = my_queue[0]
print(f"Item at the front: {front_item}") # Output: B
# --- Check if the queue is empty ---
is_empty = len(my_queue) == 0
print(f"Is the queue empty? {is_empty}") # Output: False
# --- Get the size ---
size = len(my_queue)
print(f"Size of the queue: {size}") # Output: 2
Why is deque good?
append(item): Adds an item to the right end. Time Complexity: O(1)popleft(): Removes and returns an item from the left end. Time Complexity: O(1)- It's highly optimized and implemented in C, making it very fast.
Another Option: The queue.Queue Class
Python also has a built-in queue module, which provides a Queue class. This is different from collections.deque.
When should you use queue.Queue?
Use queue.Queue when you are working with multiple threads. It's designed specifically for thread-safe communication between different parts of a concurrent program.
It includes features that deque does not, such as:
- Blocking operations: If you try to
get()from an empty queue, the thread will automatically wait (block) until an item is available. This is essential for producer-consumer patterns. - Thread safety: All its methods are atomic, meaning you don't have to worry about race conditions.
How to Use queue.Queue
import queue
import time
import threading
# Create a thread-safe Queue
my_thread_safe_queue = queue.Queue()
# --- Enqueue (put) ---
my_thread_safe_queue.put('Task 1')
my_thread_safe_queue.put('Task 2')
print(f"Queue size: {my_thread_safe_queue.qsize()}") # Output: 2
# --- Dequeue (get) ---
# This will block if the queue is empty, but it's not empty now.
task = my_thread_safe_queue.get()
print(f"Got task: {task}") # Output: Task 1
# --- Peek (not directly available, but you can check size) ---
print(f"Queue size after get: {my_thread_safe_queue.qsize()}") # Output: 1
# Example of a blocking get in a single-threaded context
# This would pause the program forever if uncommented, because the queue is now empty.
# empty_task = my_thread_safe_queue.get_nowait() # Raises queue.Empty
# print(empty_task)
Summary and Comparison
| Feature | collections.deque |
queue.Queue |
Python list |
|---|---|---|---|
| Primary Use Case | General-purpose, fast appends/pops from ends. | Multi-threaded applications. | Simple, in-order storage (not as a queue). |
| Enqueue Method | append() |
put() |
append() |
| Dequeue Method | popleft() |
get() |
pop(0) |
| Peek Method | my_deque[0] |
my_queue.queue[0] (not recommended) |
my_list[0] |
| Time Complexity (Enqueue) | O(1) | O(1) | O(1) |
| Time Complexity (Dequeue) | O(1) | O(1) | O(n) (Bad) |
| Thread Safety | No. Not safe for concurrent access. | Yes. Fully thread-safe. | No. Not safe for concurrent access. |
| Blocking Operations | No. | Yes. get() and put() can block. |
No. |
Final Recommendation
- For a single-threaded application: Use
collections.deque. It's fast, simple, and the standard choice for implementing a queue in Python. - For a multi-threaded application: Use
queue.Queue. It's the only safe and correct choice for thread-safe communication. - Avoid using
listfor queue-like operations where you frequently add to the end and remove from the beginning.
