A deque (pronounced “deck”) is short for “double-ended queue”—a linear data structure that allows insertion and deletion at both the front and the rear. Think of it as a hybrid between a stack and a queue, offering the flexibility to add or remove elements from either end.
This versatility makes deques powerful for problems requiring sliding windows, palindrome checking, and maintaining ordered sequences with efficient operations at both ends. By the end of this tutorial, you’ll understand when deques shine and how to implement them efficiently.
Deques provide O(1) time complexity for insertion and deletion at both ends, making them more flexible than stacks or queues while maintaining the same efficiency.
What is a deque?
A deque is a generalized queue that supports four primary operations:
addFront (or pushFront): Add an element to the front
addRear (or pushRear): Add an element to the rear
removeFront (or popFront): Remove and return the element from the front
removeRear (or popRear): Remove and return the element from the rear
Additional operations include:
5. peekFront: View the front element without removing it
6. peekRear: View the rear element without removing it
7. isEmpty: Check if the deque is empty
8. size: Get the number of elements
The key feature: bidirectional access allows adding and removing from both ends efficiently.
Why use a deque?
Choose a deque when:
You need efficient operations at both ends (unlike queues or stacks)
You’re implementing sliding window algorithms
You need a data structure that can act as both a stack and a queue
You’re checking for palindromes or processing sequences bidirectionally
You’re implementing work-stealing algorithms in concurrent systems
You need to maintain a limited-size buffer with eviction from either end
Avoid a deque when:
You only need single-end access (use a stack or queue)
Many languages provide optimized deque implementations:
// JavaScript doesn't have a built-in deque, but arrays work reasonably well
// for small deques (though shift/unshift are O(n))
constdeque= [];
// Add operations
deque.push(10); // Add rear
deque.unshift(5); // Add front
// Remove operations
constfront=deque.shift(); // Remove front
constrear=deque.pop(); // Remove rear
// Peek operations
constpeekFront=deque[0];
constpeekRear=deque[deque.length-1];
console.log(deque);
import java.util.ArrayDeque;import java.util.Deque;// Java's ArrayDeque is the standard implementation
Deque<Integer> deque =new ArrayDeque<>();// Add operations
deque.addLast(10);// Add rear - O(1)
deque.addFirst(5);// Add front - O(1)
// Alternative: offerFirst(), offerLast()
// Remove operations
int front = deque.removeFirst();// Remove front - O(1)
int rear = deque.removeLast();// Remove rear - O(1)
// Alternative: pollFirst(), pollLast()
// Peek operations
deque.addLast(20);deque.addLast(30);int peekFront = deque.peekFirst();// Front element
int peekRear = deque.peekLast();// Rear element
System.out.println(deque);
Common deque problems
1. Palindrome checker
Check if a string is a palindrome by comparing from both ends:
functionisPalindrome(str) {
constdeque=str.toLowerCase().replace(/[^a-z0-9]/g, '').split('');
while (deque.length>1) {
if (deque.shift() !==deque.pop()) {
returnfalse;
}
}
returntrue;
}
console.log(isPalindrome("A man, a plan, a canal: Panama")); // true
console.log(isPalindrome("race a car")); // false
from collections import deque
defis_palindrome(s):
# Clean and convert to deque cleaned =''.join(c.lower() for c in s if c.isalnum())
d = deque(cleaned)
while len(d) >1:
if d.popleft() != d.pop():
returnFalsereturnTrueprint(is_palindrome("A man, a plan, a canal: Panama")) # Trueprint(is_palindrome("race a car")) # False
import java.util.ArrayDeque;import java.util.Deque;publicclassPalindromeChecker{publicstaticbooleanisPalindrome(String s){// Clean and convert to deque
Deque<Character> deque =new ArrayDeque<>();for(char c : s.toLowerCase().toCharArray()){if(Character.isLetterOrDigit(c)){ deque.addLast(c);}}while(deque.size()> 1){if(!deque.removeFirst().equals(deque.removeLast())){returnfalse;}}returntrue;}publicstaticvoidmain(String[] args){ System.out.println(isPalindrome("A man, a plan, a canal: Panama"));// true
System.out.println(isPalindrome("race a car"));// false
}}
2. Sliding window maximum
Find the maximum value in each sliding window of size k:
functionmaxSlidingWindow(nums, k) {
constresult= [];
constdeque= []; // Stores indices
for (leti=0; i<nums.length; i++) {
// Remove indices outside the window
if (deque.length>0&&deque[0] <i-k+1) {
deque.shift();
}
// Remove smaller elements from rear (they won't be max)
while (deque.length>0&&nums[deque[deque.length-1]] <nums[i]) {
deque.pop();
}
deque.push(i);
// Add to result once we have a full window
if (i>=k-1) {
result.push(nums[deque[0]]);
}
}
returnresult;
}
console.log(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3));
// [3, 3, 5, 5, 6, 7]
from collections import deque
defmax_sliding_window(nums, k):
result = []
dq = deque() # Stores indicesfor i in range(len(nums)):
# Remove indices outside the windowif dq and dq[0] < i - k +1:
dq.popleft()
# Remove smaller elements from rearwhile dq and nums[dq[-1]] < nums[i]:
dq.pop()
dq.append(i)
# Add to result once we have a full windowif i >= k -1:
result.append(nums[dq[0]])
return result
print(max_sliding_window([1,3,-1,-3,5,3,6,7], 3))
# [3, 3, 5, 5, 6, 7]
import java.util.*;publicclassSlidingWindowMax{publicstaticint[]maxSlidingWindow(int[] nums,int k){if(nums ==null|| nums.length== 0)returnnewint[0];int[] result =newint[nums.length- k + 1]; Deque<Integer> deque =new ArrayDeque<>();// Stores indices
for(int i = 0; i < nums.length; i++){// Remove indices outside the window
if(!deque.isEmpty()&& deque.peekFirst()< i - k + 1){ deque.pollFirst();}// Remove smaller elements from rear
while(!deque.isEmpty()&& nums[deque.peekLast()]< nums[i]){ deque.pollLast();} deque.offerLast(i);// Add to result once we have a full window
if(i >= k - 1){ result[i - k + 1]= nums[deque.peekFirst()];}}return result;}publicstaticvoidmain(String[] args){int[] result = maxSlidingWindow(newint[]{1,3,-1,-3,5,3,6,7}, 3); System.out.println(Arrays.toString(result));// [3, 3, 5, 5, 6, 7]
}}
3. Implement a circular tour (gas stations problem)
Use a deque to find the starting point for a circular tour:
functioncanCompleteCircuit(gas, cost) {
constn=gas.length;
lettotalGas=0, totalCost=0;
letstart=0, tank=0;
for (leti=0; i<n; i++) {
totalGas+=gas[i];
totalCost+=cost[i];
tank+=gas[i] -cost[i];
// If tank is negative, we can't reach next station
if (tank<0) {
start=i+1;
tank=0;
}
}
returntotalGas>=totalCost?start:-1;
}
console.log(canCompleteCircuit([1,2,3,4,5], [3,4,5,1,2])); // 3
defcan_complete_circuit(gas, cost):
n = len(gas)
total_gas, total_cost =0, 0 start, tank =0, 0for i in range(n):
total_gas += gas[i]
total_cost += cost[i]
tank += gas[i] - cost[i]
# If tank is negative, we can't reach next stationif tank <0:
start = i +1 tank =0return start if total_gas >= total_cost else-1print(can_complete_circuit([1,2,3,4,5], [3,4,5,1,2])) # 3
publicclassCircularTour{publicstaticintcanCompleteCircuit(int[] gas,int[] cost){int n = gas.length;int totalGas = 0, totalCost = 0;int start = 0, tank = 0;for(int i = 0; i < n; i++){ totalGas += gas[i]; totalCost += cost[i]; tank += gas[i]- cost[i];// If tank is negative, we can't reach next station
if(tank < 0){ start = i + 1; tank = 0;}}return totalGas >= totalCost ? start :-1;}publicstaticvoidmain(String[] args){ System.out.println(canCompleteCircuit(newint[]{1,2,3,4,5},newint[]{3,4,5,1,2}));// 3
}}
Common pitfalls
Array-based front operations are O(n): Using array unshift() or shift() is inefficient. Use doubly linked lists or built-in deques.
Forgetting to update both pointers: When removing the last element, ensure both front and rear are set to null
Not checking for empty deque: Always verify before removing or peeking
Confusing which end is which: Clearly document front vs rear in your implementation
Memory management: In manual-memory languages, free nodes properly