Skip to content

collections.deque

collections.deque (pronounced β€œdeck”) is a list-like sequence optimized for fast appends and pops from both ends β€” O(1) at the front and the back. A plain list is only fast (O(1) amortized) at the end; operations at the front are O(n) because every remaining element has to shift.

LEFT end RIGHT end
β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”
appendleft ──► β”‚ A β”‚ ←→ β”‚ B β”‚ ←→ β”‚ C β”‚ ◄── append
popleft ◄── β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ pop ──►
β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜
queue = [1, 2, 3]
queue.insert(0, 0) # O(n) -- every element shifts right to make room
first = queue.pop(0) # O(n) -- every remaining element shifts left
from collections import deque
queue = deque([1, 2, 3])
queue.appendleft(0) # O(1)
first = queue.popleft() # O(1)
queue.append(4) # O(1) -- still fast at the right end too
last = queue.pop() # O(1)
print(queue) # deque([1, 2, 3, 4])
# A common real use: a sliding window / recent-history buffer
recent = deque(maxlen=3) # bounded β€” oldest item auto-evicted once full
for i in range(5):
recent.append(i)
print(recent) # deque([2, 3, 4], maxlen=3)
  • O(1) at both ends: list.insert(0, x) and list.pop(0) are O(n) because the whole list shifts; deque.appendleft/popleft are O(1) because a deque is a doubly-linked block structure, not a single contiguous array.
  • Built-in bounded buffer: deque(maxlen=N) automatically drops from the opposite end once full β€” a ready-made β€œkeep the last N items” structure without manual trimming.
  • Standard tool for queues and BFS: implementing a FIFO queue or breadth-first search’s frontier with a plain list and pop(0) is a classic performance mistake; deque is the correct structure for both.

Python’s deque is implemented as a doubly-linked list of fixed-size blocks (not a simple linked list of individual nodes) β€” each block holds a small array of elements (64 by default). Growing either end allocates a new block instead of reallocating the whole structure, which is what gives both ends O(1) amortized cost.

Block 0 Block 1 Block 2
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ [_,_,A,B]│◄──►│ [C,D,E,F]│◄──►│ [G,H,_,_]β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–² left end right end β–²
Operation list deque
────────────── ────── ──────
append (right) O(1)* O(1)
pop (right) O(1) O(1)
insert (left) O(n) ⚠️ O(1) βœ…
pop (left) O(n) ⚠️ O(1) βœ…
index access [i] O(1) βœ… O(n) ⚠️
slicing [a:b] O(k) βœ… ❌ not supported
len() O(1) O(1)
contains (in) O(n) O(n)
* O(1) amortized β€” occasional resize is O(n)
MethodDescriptionTime
append(x)Add x to the rightO(1)
appendleft(x)Add x to the leftO(1)
pop()Remove and return from rightO(1)
popleft()Remove and return from leftO(1)
extend(iterable)Extend right from iterableO(k)
extendleft(iterable)Extend left (reverses order)O(k)
rotate(n)Rotate n steps right (negative = left)O(n)
clear()Remove all elementsO(n)
count(x)Count occurrences of xO(n)
index(x)Find first position of xO(n)
insert(i, x)Insert at position iO(n)
remove(x)Remove first occurrence of xO(n)

Use append to enqueue and popleft to dequeue. Never use list.pop(0) for this β€” it’s O(n).

queue = deque()
queue.append("task-A")
queue.append("task-B")
queue.popleft() # "task-A" β€” processed in order

BFS requires a queue. Using list.pop(0) in BFS makes it O(nΒ²); deque.popleft() keeps it O(V + E).

def bfs(graph, start):
visited = {start}
queue = deque([start])
order = []
while queue:
node = queue.popleft()
order.append(node)
for neighbour in graph[node]:
if neighbour not in visited:
visited.add(neighbour)
queue.append(neighbour)
return order

maxlen automatically drops old items as new ones arrive β€” useful for a moving average or β€œlast N events” buffer:

def moving_average(data, window_size):
window = deque(maxlen=window_size)
averages = []
for value in data:
window.append(value)
averages.append(sum(window) / len(window))
return averages
print(moving_average([1, 2, 3, 4, 5, 6], 3))
# [1.0, 1.5, 2.0, 3.0, 4.0, 5.0]

rotate(n) shifts all elements n positions to the right (negative = left) β€” useful for round-robin scheduling and circular buffers:

d = deque([1, 2, 3, 4, 5])
d.rotate(2)
print(d) # deque([4, 5, 1, 2, 3])
PythonC# equivalentNotes
deque (general)LinkedList<T>Doubly-linked, O(1) at both ends
deque as queueQueue<T>Enqueue/Dequeue β†’ append/popleft
deque as stackStack<T>Push/Pop β†’ append/pop
deque(maxlen=n)Queue<T> + manual trimNo built-in bounded queue in C#
  • deque does not support fast indexed access into the middle β€” deque[500] is O(n), unlike list[500] which is O(1). Use a deque when you mostly push/pop from the ends, and a list when you need random access by index.
  • deque does not support slicing (d[1:3] raises TypeError) β€” convert to a list first (list(d)[1:3]) if you need a slice.
  • extendleft reverses order: deque([1,2,3]).extendleft([4,5,6]) gives deque([6, 5, 4, 1, 2, 3]), not [4, 5, 6, 1, 2, 3], because each appendleft shifts existing items right one at a time.
  • deque(maxlen=N) silently discards the oldest item from the opposite end when a new item is added past capacity β€” there’s no error, so an unintended maxlen can quietly lose data if you’re not expecting the eviction.
  • deque is thread-safe for individual append/pop operations from either end (an implementation detail of CPython that many use for simple producer/consumer patterns), though it’s not a substitute for proper synchronization in more complex cases like check-then-pop.
  1. What’s the time complexity of list.pop(0) versus deque.popleft()?

    Answer`list.pop(0)` is `O(n)` (every remaining element shifts); `deque.popleft()` is `O(1)`.
  2. Why is deque a poor choice if you need frequent random access by index?

    AnswerIndexed access into the middle of a `deque` is `O(n)`, unlike a `list`'s `O(1)` β€” `deque` is optimized for the ends, not the middle.
  3. What happens when you append() to a deque(maxlen=3) that already has 3 items?

    AnswerThe oldest item (at the opposite end) is automatically and silently discarded to make room β€” no error is raised.