A deque (double-ended queue, pronounced “deck”) is a queue where you can add and remove elements from both ends — the head and the tail. It generalizes both stacks and queues: you can use it as a stack (push and pop from one end), as a queue (push from one end, pop from the other), or as something more flexible.
Operations:
enqueueHead(item)— add at the front.enqueueTail(item)— add at the back.dequeueHead()— remove from the front.dequeueTail()— remove from the back.getHead()/getTail()— peek at either end.
Naturally implemented with a Doubly linked list (each node has both next and prev pointers), or with a circular array.
Implementation with doubly linked list
struct item {
int value;
struct item *next;
struct item *prev;
};
struct item *head = NULL;
struct item *tail = NULL;
void enqueueHead(int n) {
struct item *pnew = malloc(sizeof(struct item));
pnew->value = n;
pnew->next = head;
pnew->prev = NULL;
if (head != NULL) {
head->prev = pnew;
}
head = pnew;
if (tail == NULL) { // empty deque → set tail too
tail = pnew;
}
}
void enqueueTail(int n) {
struct item *pnew = malloc(sizeof(struct item));
pnew->value = n;
pnew->next = NULL;
pnew->prev = tail;
if (tail != NULL) {
tail->next = pnew;
}
tail = pnew;
if (head == NULL) { // empty deque → set head too
head = pnew;
}
}The prev pointers are essential — when you dequeueTail, you need to update the new tail’s next to NULL, and to find the new tail you need its predecessor.
bool dequeueHead(int *n) {
if (head == NULL) return false;
struct item *temp = head;
*n = head->value;
head = head->next;
if (head != NULL) {
head->prev = NULL;
} else {
tail = NULL; // deque is now empty
}
free(temp);
return true;
}
bool dequeueTail(int *n) {
if (tail == NULL) return false;
struct item *temp = tail;
*n = tail->value;
tail = tail->prev;
if (tail != NULL) {
tail->next = NULL;
} else {
head = NULL;
}
free(temp);
return true;
}All four operations are O(1) thanks to the doubly linked list’s prev pointers.
When deques are useful
- Sliding window algorithms. Maintain a window over a stream by enqueueing at one end and dequeueing from the other when the window slides.
- Work-stealing schedulers. Each thread has its own deque of tasks; it pushes/pops from one end, and other threads “steal” from the other end.
- Palindrome checking. Push characters at both ends; remove and compare from each end.
- Undo/redo. Undo from one end, redo from the other.
Implementation with circular array
A circular array deque uses two pointers (front and back) plus modular arithmetic, similar to a circular queue but with operations at both ends. The complication is that the array doesn’t naturally extend leftward — front - 1 underflows. Modular arithmetic handles this: (front - 1 + capacity) % capacity gives the cell just before front.
Pros of circular array: contiguous memory, cache-friendly.
Cons: fixed capacity (or expensive to resize).
C++ std::deque and Python’s collections.deque are the standard library implementations.