Skip to content

Deques and Priority Queues

A deque (double-ended queue) is a linear collection that supports insertion and removal at both Ends. It generalises both stacks (LIFO) and queues (FIFO).

OperationDescriptionArray-backedLinked-list
push_front(x)Insert at frontO(1)O(1)O(1)O(1)
push_back(x)Insert at backO(1)O(1)O(1)O(1)
pop_front()Remove from frontO(1)O(1)O(1)O(1)
pop_back()Remove from backO(1)O(1)O(1)O(1)
front()Access front elementO(1)O(1)O(1)O(1)
back()Access back elementO(1)O(1)O(1)O(1)
is_empty()Check if emptyO(1)O(1)O(1)O(1)
size()Number of elementsO(1)O(1)O(1)O(1)

A circular buffer (ring buffer) implements a deque using a fixed-size array with two indices (head And tail) that wrap around.

class CircularBufferDeque:
"""
Deque using a circular buffer (dynamic array).
Time: O(1) amortised for all operations
Space: O(n)
"""
def __init__(self, capacity=16):
self.capacity = capacity
self.data = [None] * capacity
self.head = 0
self.tail = 0
self.size = 0
def push_front(self, value):
if self.size == self.capacity:
self._resize()
self.head = (self.head - 1) % self.capacity
self.data[self.head] = value
self.size += 1
def push_back(self, value):
if self.size == self.capacity:
self._resize()
self.data[self.tail] = value
self.tail = (self.tail + 1) % self.capacity
self.size += 1
def pop_front(self):
if self.size == 0:
raise IndexError("pop from empty deque")
value = self.data[self.head]
self.head = (self.head + 1) % self.capacity
self.size -= 1
return value
def pop_back(self):
if self.size == 0:
raise IndexError("pop from empty deque")
self.tail = (self.tail - 1) % self.capacity
value = self.data[self.tail]
self.size -= 1
return value
def front(self):
if self.size == 0:
raise IndexError("front of empty deque")
return self.data[self.head]
def back(self):
if self.size == 0:
raise IndexError("back of empty deque")
return self.data[(self.tail - 1) % self.capacity]
def _resize(self):
new_data = [None] * (self.capacity * 2)
for i in range(self.size):
new_data[i] = self.data[(self.head + i) % self.capacity]
self.data = new_data
self.head = 0
self.tail = self.size
self.capacity *= 2
graph LR
    subgraph Circular Buffer
        direction LR
        A[5] --> B[12] --> C[7] --> D[3] --> E[.] --> F[.] --> G[.] --> H[.]
    end
    HEAD["head=2"] -.-> C
    TAIL["tail=5"] -.-> E
class DequeNode:
__slots__ = ("val', 'prev', 'next')
def __init__(self, val):
self.val = val
self.prev = None
self.next = None
class LinkedListDeque:
"""
Deque using a doubly-linked list with sentinel nodes.
Time: O(1) for all operations
Space: O(n) — one node per element plus two sentinels
"""
def __init__(self):
self.sentinel = DequeNode(None)
self.sentinel.prev = self.sentinel
self.sentinel.next = self.sentinel
self.size = 0
def push_front(self, value):
node = DequeNode(value)
node.next = self.sentinel.next
node.prev = self.sentinel
self.sentinel.next.prev = node
self.sentinel.next = node
self.size += 1
def push_back(self, value):
node = DequeNode(value)
node.prev = self.sentinel.prev
node.next = self.sentinel
self.sentinel.prev.next = node
self.sentinel.prev = node
self.size += 1
def pop_front(self):
if self.size == 0:
raise IndexError("pop from empty deque")
node = self.sentinel.next
node.next.prev = self.sentinel
self.sentinel.next = node.next
self.size -= 1
return node.val
def pop_back(self):
if self.size == 0:
raise IndexError("pop from empty deque")
node = self.sentinel.prev
node.prev.next = self.sentinel
self.sentinel.prev = node.prev
self.size -= 1
return node.val
LanguageTypeImplementationNotes
Pythoncollections.dequeCircular bufferO(1)O(1) all operations
C++std::dequeSegmented arrayO(1)O(1) all operations
JavaArrayDequeCircular bufferO(1)O(1) all operations
RustVecDequeRing bufferO(1)O(1) all operations
GoNone (use slice)N/AManual implementation needed