Back to Catalog
Data Structures
FIFO
Deque (Double-Ended Queue)
Push and pop from both ends in O(1) — a stack and queue in one structure.
Intent & Description
Real-world Use Case
Sliding window maximum — use a monotonic deque to compute max of every k-element window in O(n) total vs O(n²) naive. Work-stealing thread pool — each thread has a deque, steals from the back of other threads’ deques. Palindrome check — push all chars, pop from both ends and compare.
Source
📌 TL;DR
Deque = stack + queue in one. O(1) at both ends. Master the monotonic deque pattern for O(n) sliding window problems. If you only need one end, use a plain stack or queue.
Advantages
- O(1) operations at both ends — the union of stack and queue capabilities
- Enables the powerful monotonic deque pattern for sliding window problems
- Single structure replaces stack + queue when you need both behaviors
- Flexible — can enforce LIFO or FIFO discipline on the same structure
Disadvantages
- More complex to implement than a plain stack or queue
- Doubly linked list backing has pointer overhead and poor cache locality
- Easy to misuse — if you only need one end, use a stack or queue for clarity
- No random access — middle elements are still O(n) to reach
Implementation Example
// ─── Deque backed by doubly linked list ──────────────────────
class DequeNode {
constructor(value) { this.value = value; this.prev = null; this.next = null; }
}
class Deque {
#head = null;
#tail = null;
#size = 0;
pushFront(value) {
const node = new DequeNode(value);
if (!this.#head) { this.#head = this.#tail = node; }
else { node.next = this.#head; this.#head.prev = node; this.#head = node; }
this.#size++;
}
pushBack(value) {
const node = new DequeNode(value);
if (!this.#tail) { this.#head = this.#tail = node; }
else { node.prev = this.#tail; this.#tail.next = node; this.#tail = node; }
this.#size++;
}
popFront() {
if (!this.#head) return null;
const val = this.#head.value;
this.#head = this.#head.next;
if (this.#head) this.#head.prev = null; else this.#tail = null;
this.#size--;
return val;
}
popBack() {
if (!this.#tail) return null;
const val = this.#tail.value;
this.#tail = this.#tail.prev;
if (this.#tail) this.#tail.next = null; else this.#head = null;
this.#size--;
return val;
}
peekFront() { return this.#head?.value ?? null; }
peekBack() { return this.#tail?.value ?? null; }
isEmpty() { return this.#size === 0; }
get size() { return this.#size; }
}
// ─── Monotonic Deque: Sliding Window Maximum in O(n) ─────────
// ❌ Naive O(n²): for each window, scan k elements for max
// ✅ Monotonic deque O(n): maintain decreasing deque of indices
function slidingWindowMax(nums, k) {
const dq = new Deque(); // Stores indices, front = current max index
const result = [];
for (let i = 0; i < nums.length; i++) {
// Remove indices outside the current window
while (!dq.isEmpty() && dq.peekFront() < i - k + 1) dq.popFront();
// Remove indices whose values are smaller than current (they can never be max)
while (!dq.isEmpty() && nums[dq.peekBack()] < nums[i]) dq.popBack();
dq.pushBack(i);
if (i >= k - 1) result.push(nums[dq.peekFront()]); // Front is always max
}
return result;
}
console.log(slidingWindowMax([1,3,-1,-3,5,3,6,7], 3));
// [3, 3, 5, 5, 6, 7]