Deques

intermediate25 min

Both ends

A stack works at one end. A queue adds at one end and removes at the other. A deque — short for double-ended queue, pronounced “deck” — does everything at both ends.

That makes it a superset. Any stack or queue can be built from a deque by simply choosing which methods you call. This is why the two previous lessons both used ArrayDeque.

Deque methods

FrontBackBehaviour when empty or absent
addFirstaddLastThrows if capacity-limited
offerFirstofferLastReturns false
removeFirstremoveLastThrows
pollFirstpollLastReturns null
getFirstgetLastThrows
peekFirstpeekLastReturns null

Six operations in two families: the throwing kind and the returning kind. Choose one family per program.

Using one

import java.util.ArrayDeque;
import java.util.Deque;

Deque<String> deque = new ArrayDeque<>();

deque.addLast("middle");
deque.addFirst("start");
deque.addLast("end");

System.out.println(deque);              // [start, middle, end]
System.out.println(deque.peekFirst());  // start
System.out.println(deque.peekLast());   // end

deque.removeFirst();
deque.removeLast();
System.out.println(deque);              // [middle]

Adding at the front costs the same as adding at the back — both instant. That is the thing an ArrayList cannot do, where inserting at index 0 shifts every element.

As a stack, as a queue

// stack: add and remove at the same end
Deque<Integer> stack = new ArrayDeque<>();
stack.addFirst(1);
stack.addFirst(2);
System.out.println(stack.removeFirst());   // 2 — last in, first out

// queue: add at one end, remove at the other
Deque<Integer> queue = new ArrayDeque<>();
queue.addLast(1);
queue.addLast(2);
System.out.println(queue.removeFirst());   // 1 — first in, first out

Same class, same object type, different method pairing.

push and pop are front operations

Deque also provides push, pop, and peek for stack-style code. Be aware of what they map to:

  • push(x) is addFirst(x)
  • pop() is removeFirst()
  • peek() is peekFirst()

So push adds at the front, not the back. If you mix push with addLast on the same deque you will get results that look random. Decide whether you are treating the object as a stack or a queue and use one vocabulary throughout.

Which implementation

ClassUse when
ArrayDequeDefault choice — fastest, least memory
LinkedListOnly if you need null elements or List methods too
ArrayBlockingQueuePassing work between threads safely

ArrayDeque stores elements in an array used as a circular buffer. Instead of shifting elements when the front is removed, it moves an internal index and wraps around at the end. That is how both ends stay instant while keeping array-like memory layout.

It rejects null, which is deliberate: the poll and peek methods use null to mean “empty”, so allowing null elements would make that signal ambiguous.

Where deques show up

A sliding window. Keeping the maximum of the last K values needs removal from both ends — new values push in at the back, stale ones drop off the front.

// indices of candidates for the maximum in each window of width k
Deque<Integer> window = new ArrayDeque<>();

for (int i = 0; i < values.length; i++) {
    // drop indices that have fallen out of the window
    while (!window.isEmpty() && window.peekFirst() <= i - k) {
        window.removeFirst();
    }
    // drop values smaller than the incoming one — they can never be the max
    while (!window.isEmpty() && values[window.peekLast()] <= values[i]) {
        window.removeLast();
    }
    window.addLast(i);

    if (i >= k - 1) {
        System.out.println("window max: " + values[window.peekFirst()]);
    }
}

Both ends are used for different purposes: the front discards expired entries, the back discards values that are now irrelevant. No other structure does this cleanly.

Undo and redo. Two deques, moving items between them.

Browser history. Back and forward, from either end.

A bounded buffer of recent readings. Add at the back, and drop from the front once you exceed the limit.

Deque<Double> recent = new ArrayDeque<>();

void record(double reading) {
    recent.addLast(reading);
    if (recent.size() > 50) {
        recent.removeFirst();      // discard the oldest
    }
}

That last pattern is genuinely common in robot code, where you want a rolling average of the last N sensor values without letting the buffer grow forever.

Common mistakes

  • Mixing push/pop with addLast/removeLast on the same object.
  • Mixing the throwing and returning method families.
  • Adding null to an ArrayDeque.
  • Assuming iteration order matches insertion order at both ends. Iterating a Deque goes front to back, so items added with addFirst appear in reverse of the order you added them.
  • Reaching for LinkedList when ArrayDeque is faster.

Practice

  1. Add the values 1 through 5 alternately to the front and back of a deque, then print it. Predict the output before running it.
  2. Write a method that checks whether a String is a palindrome by comparing characters from both ends of a deque.
  3. Write a class holding the last 20 readings, dropping the oldest when a twenty-first arrives, with a method returning their average.
  4. Implement undo and redo with two deques: doAction, undo, and redo.
  5. Given an int[] and a window width k, print the maximum of every window of that width.
Hints
  1. Work it out on paper. addFirst reverses the order those items appear in.
  2. Push every character, then repeatedly removeFirst and removeLast and compare. Stop when fewer than two remain — an odd-length string leaves a middle character that needs no partner.
  3. addLast then trim from the front. For the average, iterate and divide by size().
  4. doAction pushes onto the undo deque and clears redo. undo moves one item from undo to redo. redo moves it back. Clearing redo on a new action is the part people miss — it is also what real editors do.
  5. The sliding-window maximum above. Each index is added once and removed once, so the whole pass is linear despite the inner loops.

Next

Related