What is a deque?
A deque (double-ended queue, pronounced “deck”) lets you insert and remove elements at both ends in O(1): push_front, push_back, pop_front and pop_back. It generalizes the other two structures — use only one end and it is a stack; push at the back and pop at the front and it is a queue.
How it works
- Store the elements in a circular array of
capacityslots withfront(index of the first element) andsize. The last element lives at(front + size - 1) % capacity. - push_front(x): step
frontback withfront = (front - 1 + capacity) % capacity, writeA[front] = xand increasesize. - push_back(x): write to the first free slot after the last element,
A[(front + size) % capacity] = x, and increasesize. - pop_front(): read
A[front], movefront = (front + 1) % capacityand decreasesize. - pop_back(): read
A[(front + size - 1) % capacity]and decreasesize;frontdoes not move.
Worked example: pushing past the start of the array
Capacity 7, holding 8, 3 and 5 at indices 0–2 (front = 0, size = 3):
push_front(1): front = (0 − 1 + 7) % 7 = 6 — it wraps to the last slot.A[6] = 1. Deque: [1, 8, 3, 5].push_front(2): front = 5 andA[5] = 2. Deque: [2, 1, 8, 3, 5].push_back(9): writesA[(5 + 5) % 7] = A[3] = 9. Deque: [2, 1, 8, 3, 5, 9].pop_back(): readsA[(5 + 6 - 1) % 7] = A[3]and returns 9; size 5.pop_front(): returns 2 from index 5; front becomes 6.
Nothing ever shifts. Each operation moves one index and touches one slot, so all four are O(1).
One structure, three behaviours
- As a stack:
push_backwithpop_back(or the two front operations) — last in, first out. - As a queue:
push_backwithpop_front— first in, first out. - As both at once: problems such as the sliding-window maximum add at the back and remove from both ends.
Restricted forms appear in textbooks too: an input-restricted deque allows insertion at only one end, and an output-restricted deque allows deletion at only one end.
How real deques are implemented
- Circular array — this page, Java's
ArrayDeque, Rust'sVecDeque: compact and cache-friendly. When full it moves the elements, in order, into a larger array, so pushes are amortized O(1). - Blocks of fixed-size arrays — C++'s
std::dequeand Python'scollections.deque: pushing at either end never moves existing elements. C++ also gives O(1) indexing, while indexing a Python deque is O(n) towards the middle. - Doubly linked list: O(1) at both ends with no resizing, but one node allocation per element.
Classic deque problem: sliding window maximum
To report the maximum of every window of k consecutive values in O(n), keep a deque of indices whose values decrease from front to back. For each new index:
- Pop from the back while the value there is smaller than the incoming value — it can never be a window maximum again.
- Push the new index at the back.
- Pop from the front if that index has slid out of the window.
- The front now holds the index of the current window's maximum.
For [1, 3, −1, −3, 5, 3, 6, 7] with k = 3 the maxima are [3, 3, 5, 5, 6, 7]. Each index enters and leaves the deque at most once — the same amortized argument as the monotonic stack behind the next greater element. Deques also drive 0-1 BFS (0-weight edges go to the front, 1-weight edges to the back) and work-stealing thread schedulers.
Common mistakes
- Writing
(front - 1) % capacityin C, C++ or Java: when front is 0 the result is −1, notcapacity - 1. Addcapacitybefore taking the modulo. - Moving
frontonpop_back— onlysizechanges. - Forgetting that pushing several values at the front reverses them: push_front 1, then 2, gives [2, 1, …].
- Indexing into the middle of Python's
collections.dequeinside a loop — each access is O(n); use a list when you need random access. - Storing
nullin Java'sArrayDeque, which throws aNullPointerException.
Time and space complexity
| Case | Time | Space | Why |
|---|---|---|---|
| push_front / push_back | O(1) | O(1) | One index step (mod capacity) and one write; amortized O(1) if the array grows. |
| pop_front / pop_back | O(1) | O(1) | Read one end, then move front or just shrink size. |
| peek_front / peek_back | O(1) | O(1) | Direct index into the array. |
| access by position i | O(1) | O(1) | A[(front + i) % capacity] in a circular array; O(n) in a linked list. |
| search for a value | O(n) | O(1) | Scan from front to back. |
Deque in Python
class Deque:
"""Double-ended queue on a circular array: O(1) at both ends."""
def __init__(self, capacity):
self.A = [None] * capacity
self.capacity = capacity
self.front = 0 # first element
self.size = 0 # last element: (front + size - 1) % capacity
def push_front(self, x):
if self.size == self.capacity:
raise IndexError("Deque full")
self.front = (self.front - 1 + self.capacity) % self.capacity # step back
self.A[self.front] = x
self.size = self.size + 1
def push_back(self, x):
if self.size == self.capacity:
raise IndexError("Deque full")
rear = (self.front + self.size) % self.capacity # first free slot after the last
self.A[rear] = x
self.size = self.size + 1
def pop_front(self):
if self.size == 0:
raise IndexError("Deque empty")
x = self.A[self.front]
self.front = (self.front + 1) % self.capacity
self.size = self.size - 1
return x
def pop_back(self):
if self.size == 0:
raise IndexError("Deque empty")
rear = (self.front + self.size - 1) % self.capacity
x = self.A[rear]
self.size = self.size - 1 # front stays where it is
return x
def peek_front(self):
if self.size == 0:
raise IndexError("Deque empty")
return self.A[self.front]
def peek_back(self):
if self.size == 0:
raise IndexError("Deque empty")
return self.A[(self.front + self.size - 1) % self.capacity]
def clear(self):
self.front = 0
self.size = 0
def is_empty(self):
return self.size == 0
def is_full(self):
return self.size == self.capacity
# d = Deque(7); d.push_back(8); d.push_front(1); d.pop_back() -> 8The visualizer above shows the same code in Python, C++, Java and JavaScript.
Frequently asked questions
What is a deque?
A double-ended queue: a sequence that supports insertion and removal at both the front and the back in O(1) time.
How do you pronounce deque?
Like “deck”. It is short for double-ended queue and is a different word from *dequeue*, the queue operation.
Is a deque a stack or a queue?
Both: using one end only gives stack (LIFO) behaviour, and adding at one end while removing at the other gives queue (FIFO) behaviour.
What is the time complexity of deque operations?
Push and pop at either end are O(1) (amortized when the backing array grows), peeking at the ends is O(1), and searching is O(n).
When should I use collections.deque in Python?
Whenever you add or remove at the left end: appendleft and popleft are O(1), whereas list.insert(0, x) and list.pop(0) are O(n). deque(maxlen=n) also gives a bounded buffer that drops the oldest item.
What is the difference between a deque and a circular queue?
A circular queue only adds at the rear and removes at the front. A deque on a circular array uses the same wrap-around indices but allows both operations at both ends.
Keep learning
- Circular Queue VisualizerCircular queue visualizer: watch front and rear wrap around a ring buffer with (i + 1) % capacity, and learn how to tell a full queue from an empty one.
- Queue VisualizerInteractive queue visualizer: watch enqueue and dequeue move the front and rear of an array queue, and see the wasted-space problem that circular queues fix.
- Stack VisualizerInteractive stack visualizer: watch push, pop and peek move the top pointer of an array stack, see overflow and underflow happen, and learn why each is O(1).
- Next Greater Element VisualizerFind the next greater element of every array value with a monotonic stack in O(n). Watch each index wait on the stack until a larger value resolves it.
- Linked List VisualizerInteractive linked list visualizer: watch every pointer change as you insert, delete, search and reverse singly, doubly and circular lists, with code and costs.
Last updated