collections.deque
collections.deque
Section titled β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 β βββ appendpopleft βββ β β β β β β pop βββΊ ββββββ ββββββ ββββββBefore this feature
Section titled βBefore this featureβqueue = [1, 2, 3]
queue.insert(0, 0) # O(n) -- every element shifts right to make roomfirst = queue.pop(0) # O(n) -- every remaining element shifts leftAfter this feature
Section titled βAfter this featureβ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 toolast = queue.pop() # O(1)
print(queue) # deque([1, 2, 3, 4])# A common real use: a sliding window / recent-history bufferrecent = deque(maxlen=3) # bounded β oldest item auto-evicted once fullfor i in range(5): recent.append(i)print(recent) # deque([2, 3, 4], maxlen=3)Why this is better
Section titled βWhy this is betterβ- O(1) at both ends:
list.insert(0, x)andlist.pop(0)areO(n)because the whole list shifts;deque.appendleft/popleftareO(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;dequeis the correct structure for both.
How it works internally
Section titled βHow it works internallyβ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 β²Deque vs. list β performance
Section titled βDeque vs. list β performanceβ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 supportedlen() O(1) O(1)contains (in) O(n) O(n)
* O(1) amortized β occasional resize is O(n)Core methods
Section titled βCore methodsβ| Method | Description | Time |
|---|---|---|
append(x) | Add x to the right | O(1) |
appendleft(x) | Add x to the left | O(1) |
pop() | Remove and return from right | O(1) |
popleft() | Remove and return from left | O(1) |
extend(iterable) | Extend right from iterable | O(k) |
extendleft(iterable) | Extend left (reverses order) | O(k) |
rotate(n) | Rotate n steps right (negative = left) | O(n) |
clear() | Remove all elements | O(n) |
count(x) | Count occurrences of x | O(n) |
index(x) | Find first position of x | O(n) |
insert(i, x) | Insert at position i | O(n) |
remove(x) | Remove first occurrence of x | O(n) |
Use cases
Section titled βUse casesβQueue (FIFO)
Section titled βQueue (FIFO)β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 orderBreadth-first search
Section titled βBreadth-first searchβ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 orderSliding window
Section titled βSliding windowβ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])C# equivalent
Section titled βC# equivalentβ| Python | C# equivalent | Notes |
|---|---|---|
deque (general) | LinkedList<T> | Doubly-linked, O(1) at both ends |
deque as queue | Queue<T> | Enqueue/Dequeue β append/popleft |
deque as stack | Stack<T> | Push/Pop β append/pop |
deque(maxlen=n) | Queue<T> + manual trim | No built-in bounded queue in C# |
Key notes / edge cases
Section titled βKey notes / edge casesβdequedoes not support fast indexed access into the middle βdeque[500]isO(n), unlikelist[500]which isO(1). Use adequewhen you mostly push/pop from the ends, and alistwhen you need random access by index.dequedoes not support slicing (d[1:3]raisesTypeError) β convert to a list first (list(d)[1:3]) if you need a slice.extendleftreverses order:deque([1,2,3]).extendleft([4,5,6])givesdeque([6, 5, 4, 1, 2, 3]), not[4, 5, 6, 1, 2, 3], because eachappendleftshifts 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 unintendedmaxlencan quietly lose data if youβre not expecting the eviction.dequeis thread-safe for individualappend/popoperations 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.
Quick practice
Section titled βQuick practiceβ-
Whatβs the time complexity of
list.pop(0)versusdeque.popleft()?Answer
`list.pop(0)` is `O(n)` (every remaining element shifts); `deque.popleft()` is `O(1)`. -
Why is
dequea poor choice if you need frequent random access by index?Answer
Indexed 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. -
What happens when you
append()to adeque(maxlen=3)that already has 3 items?Answer
The oldest item (at the opposite end) is automatically and silently discarded to make room β no error is raised.