ArrayDeque in Java: The Better Stack and Queue

  • Last Updated: August 13, 2026
  • By: javahandson
  • Series
img

ArrayDeque in Java: The Better Stack and Queue

Learn ArrayDeque in Java — the faster, cleaner stack and queue. See core methods, how the circular array works, and when to pick it over Stack and LinkedList.

1. Introduction

If you have ever needed a stack or a queue in Java, you probably reached for Stack or LinkedList first. Both work. But there is a faster, cleaner option that most beginners never hear about. It is called ArrayDeque in Java, and once you learn it, you may never go back.

The name looks scary. Deque is short for “double-ended queue.” You say it like “deck,” as in a deck of cards. The idea is simple though. It is a line where you can add and remove from both ends, the front and the back.

Think of a queue at a coffee shop. Normally people join at the back and leave from the front. A deque lets you do more. Someone can cut in at the front, and someone at the back can walk away. Both ends are open for business.

So why does this matter? Because a deque can act as two things at once. Use one end only, and you get a stack. Use both ends the normal way, and you get a queue. One class covers both jobs, and it does them fast.

In this guide we will build up from the basics. You will see why ArrayDeque beats the old Stack class. You will learn how it works inside and when to pick it. By the end you will treat it as your default for stacks and queues.

1.1 What This Guide Covers

Here is the road ahead:

  • What a deque is, in plain everyday terms
  • Why ArrayDeque is the better stack and the better queue
  • The core methods for adding, removing, and peeking
  • How it works inside with a resizable circular array
  • ArrayDeque vs Stack, LinkedList, and other choices
  • Common mistakes and the interview questions people ask

You only need to know what a Java class and a List are. If you have used an ArrayList before, you are more than ready for this.

ArrayDeque in Java

2. What Is ArrayDeque?

ArrayDeque is a class in the Java Collections Framework. It lives in the java.util package. It gives you a double-ended queue backed by a resizable array. In short, it is a fast, flexible line where both ends are open.

The class implements the Deque interface. Deque itself extends Queue. So an ArrayDeque is a Queue, and it is also a Deque. That family tree is why it can play so many roles.

Here is the mental model I use. Picture a row of lockers with no fixed start or end. You can drop something in at the left, drop something in at the right, and grab from either side. Nothing forces you to use just one door.

2.1 A Deque Is Two Tools in One

The real charm of a deque is how many shapes it takes. Restrict yourself to certain methods, and it becomes a specific data structure.

  • Add and remove from one end only, and you have a stack (LIFO, last in first out)
  • Add at the back and remove from the front, and you have a queue (FIFO, first in first out)
  • Use both ends freely, and you have a full double-ended queue

So you learn one class and get three tools. That is a good deal. And you do not pay for the extra power when you do not use it.

2.2 Where It Sits in the Family

Let me place it for you. At the top is the Collection interface. Under it sits Queue. Under Queue sits Deque. And ArrayDeque is a concrete class that implements Deque.

There is a sibling too. LinkedList also implements Deque. So both classes can act as a deque. They just do it in very different ways, which we will get into later.

2.3 Creating an ArrayDeque

Making one is easy. You can start empty or with a hint about size.

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

// start empty
Deque<String> deque = new ArrayDeque<>();

// give it a starting capacity hint
Deque<Integer> numbers = new ArrayDeque<>(32);

// build one from another collection
Deque<String> copy = new ArrayDeque<>(someList);

Notice the type on the left is Deque, not ArrayDeque. Coding to the interface is a good habit. It keeps your code flexible if you ever swap the class later.

3. Why Should You Use ArrayDeque?

You might wonder why you need a new class at all. Stack and LinkedList already exist. Fair question. Let us look at what ArrayDeque gives you that the others do not.

3.1 It Is Faster Than the Old Options

ArrayDeque uses a plain array inside. Arrays are friendly to the CPU because their data sits together in memory. That means fewer cache misses and quicker access.

LinkedList, by contrast, scatters nodes all over the heap. Each node holds a value plus two pointers. Jumping from node to node is slower, and all those pointers eat extra memory. For most stack and queue work, ArrayDeque simply wins.

3.2 It Beats the Legacy Stack Class

Java has an old Stack class from the very first version. It still works, but it carries baggage. Stack extends Vector, and every method on it is synchronized. That means a lock on each call, even in single-threaded code where you gain nothing from it.

ArrayDeque has no such lock. In a single thread, that makes it faster. The official Java docs even suggest it as the modern replacement for Stack. When you want a stack, this is the class to reach for.

3.3 It Rejects null, Which Catches Bugs

ArrayDeque does not allow null elements. Try to add one and you get a NullPointerException right away. This sounds annoying, but it is a feature.

The reason is subtle. Queue methods like poll() return null to signal an empty queue. If null were also a real value, you could never tell them apart. By banning null, ArrayDeque keeps that signal clean and clear.

3.4 It Grows on Its Own

You never have to size it by hand. When the internal array fills up, ArrayDeque makes a bigger one and copies everything over. This usually doubles the capacity, so the resize cost stays low across many adds.

From your seat, it just feels endless. You keep adding, and it keeps accepting. The growing happens quietly behind the scenes.

▸ Interview Insight
Q: Why is ArrayDeque preferred over the Stack class for stack operations?
A: The Stack class extends Vector, so all its methods are synchronized. That adds locking overhead you rarely need. ArrayDeque has no synchronization and uses a faster array-based design. The Java documentation recommends it as the modern stack. It is quicker in single-threaded code, which covers most real cases.

4. Core Methods You Will Use

ArrayDeque gives you a rich set of methods. Do not let the long list scare you. They come in neat pairs, and once you spot the pattern, they are easy to remember.

4.1 Two Flavors of Each Operation

Most operations come in two versions. One throws an exception when things go wrong. The other returns a special value instead, like false or null.

  • Throwing methods: addFirst, addLast, removeFirst, removeLast, getFirst, getLast
  • Non-throwing methods: offerFirst, offerLast, pollFirst, pollLast, peekFirst, peekLast

Which should you pick? Use the throwing ones when an empty deque means a real bug. Use the polling ones when empty is a normal, expected case you want to handle gently.

4.2 Adding Elements

You can push onto either end. Here are the everyday calls.

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

deque.addFirst("A");   // front:  [A]
deque.addLast("B");    // back:   [A, B]
deque.offerFirst("C"); // front:  [C, A, B]
deque.offerLast("D");  // back:   [C, A, B, D]

System.out.println(deque); // [C, A, B, D]

The First calls push onto the head. The Last calls push onto the tail. That naming stays the same across the whole class, so it is easy to keep straight.

4.3 Removing Elements

Removing pulls a value off and hands it back to you.

Deque<String> deque = new ArrayDeque<>();
deque.addLast("A");
deque.addLast("B");
deque.addLast("C");   // [A, B, C]

String first = deque.removeFirst();  // "A", deque now [B, C]
String last  = deque.pollLast();     // "C", deque now [B]

System.out.println(deque);           // [B]

Remember the difference. removeFirst() on an empty deque throws an exception. pollFirst() on an empty deque returns null instead. Choose based on how you want to treat empty.

4.4 Peeking Without Removing

Sometimes you just want to look. Peeking reads an end without taking the value out.

Deque<String> deque = new ArrayDeque<>();
deque.addLast("X");
deque.addLast("Y");   // [X, Y]

String head = deque.peekFirst();  // "X", nothing removed
String tail = deque.peekLast();   // "Y", nothing removed

System.out.println(deque);        // still [X, Y]

Peeking is handy when you want to check the next value before deciding what to do. The deque stays untouched, so you lose nothing by looking.

4.5 Checking Size and Empty

A couple of small helpers round things out. Use size() to count the elements. Use isEmpty() to check whether the deque has anything at all before you remove.

Deque<Integer> deque = new ArrayDeque<>();
deque.add(1);
deque.add(2);

System.out.println(deque.size());     // 2
System.out.println(deque.isEmpty());  // false

deque.clear();                        // remove everything
System.out.println(deque.isEmpty());  // true

Checking isEmpty() before a poll or pop is a good habit. It lets you avoid surprises and handle the empty case on your own terms.

Operation Throws on failure Returns special value Acts on
Insert at head addFirst(e) offerFirst(e) Front
Insert at tail addLast(e) offerLast(e) Back
Remove from head removeFirst() pollFirst() Front
Remove from tail removeLast() pollLast() Back
Look at head getFirst() peekFirst() Front
Look at tail getLast() peekLast() Back

Table 1: The paired methods, grouped by what they do and how they handle an empty deque.

5. Using ArrayDeque as a Stack

This is where ArrayDeque shines. A stack follows LIFO order, last in first out. Think of a stack of plates. You add to the top and take from the top. The last plate you put down is the first one you pick up.

5.1 The Stack Methods

For stack work, ArrayDeque gives you three friendly names that match the classic stack vocabulary.

  • push(e) — add an item to the top of the stack
  • pop() — remove and return the top item
  • peek() — look at the top item without removing it

Under the hood, push() is just addFirst() and pop() is just removeFirst(). Same engine, friendlier labels. You get the readable stack words without the old Stack class.

5.2 A Stack in Action

Deque<Integer> stack = new ArrayDeque<>();

stack.push(10);
stack.push(20);
stack.push(30);   // top is 30

System.out.println(stack.peek());  // 30, still on the stack
System.out.println(stack.pop());   // 30, now removed
System.out.println(stack.pop());   // 20
System.out.println(stack);         // [10]

See how clean that reads? The last value in, 30, comes out first. That is LIFO in plain sight. And because there is no synchronized lock, this runs faster than the old Stack class.

5.3 A Real Use: Undo History

Stacks show up everywhere. A classic example is an undo feature. Every action gets pushed onto a stack. When the user hits undo, you pop the last action and reverse it.

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

history.push("typed hello");
history.push("bold text");
history.push("changed color");

// user hits undo
String undo = history.pop();   // "changed color"
System.out.println("Undoing: " + undo);

The most recent action sits on top, ready to reverse first. That is exactly what undo needs. Browser back buttons and function call stacks work on the same idea.

5.4 Another Use: Matching Brackets

Stacks are great for checking balanced brackets in code or math. You scan the text left to right. Every opening bracket gets pushed. Every closing bracket pops the top and checks that it matches.

boolean isBalanced(String s) {
    Deque<Character> stack = new ArrayDeque<>();
    for (char c : s.toCharArray()) {
        if (c == '(' || c == '[' || c == '{') {
            stack.push(c);
        } else if (c == ')' || c == ']' || c == '}') {
            if (stack.isEmpty()) return false;
            char open = stack.pop();
            if (!matches(open, c)) return false;
        }
    }
    return stack.isEmpty();  // leftover opens mean unbalanced
}

The stack remembers the open brackets in order. When a close arrives, the top of the stack should be its partner. If the stack ends empty, every bracket found its match. This pattern shows up in compilers and editors all the time.

6. Using ArrayDeque as a Queue

The same class works as a queue with no fuss. A queue follows FIFO order, first in first out. Think of that coffee shop line again. The first person to join is the first one served.

6.1 The Queue Methods

For queue behavior, you add at one end and remove from the other. The common calls are simple.

  • offer(e) or add(e) — put an item at the back of the queue
  • poll() — remove and return the front item
  • peek() — look at the front item without removing it

So items enter at the tail and leave from the head. That gives you fair, in-order processing. First come, first served.

6.2 A Queue in Action

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

queue.offer("task1");
queue.offer("task2");
queue.offer("task3");   // [task1, task2, task3]

System.out.println(queue.poll());  // task1, the first one in
System.out.println(queue.poll());  // task2
System.out.println(queue);         // [task3]

The first task added is the first one handled. Nobody jumps the line. This ordering fits any job where fairness matters, like processing requests in the order they arrive.

6.3 As a Double-Ended Queue

You do not have to pick just stack or queue. You can use both ends at once. This full deque mode is useful for things like a sliding window or a work-stealing scheduler.

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

deque.offerLast(1);
deque.offerLast(2);
deque.offerFirst(0);   // [0, 1, 2]

deque.pollFirst();     // removes 0
deque.pollLast();      // removes 2
System.out.println(deque);  // [1]

Here we added and removed from both sides freely. That is the full power of a deque. Most of the time you will use one mode, but the option is always there.

6.4 A Real Use: Sliding Window

The double-ended mode powers a famous trick called the sliding window maximum. You have an array and a window that slides across it. At each step you want the largest value in the current window, and you want it fast.

A deque solves this neatly. You store array indexes, not values. As the window moves, you drop indexes that fall out of range from the front. You also drop smaller values from the back before adding a new one. The front of the deque always holds the biggest value in the window.

int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> deque = new ArrayDeque<>(); // holds indexes
    int[] result = new int[nums.length - k + 1];

    for (int i = 0; i < nums.length; i++) {
        // drop indexes outside the window from the front
        if (!deque.isEmpty() && deque.peekFirst() <= i - k) {
            deque.pollFirst();
        }
        // drop smaller values from the back
        while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
            deque.pollLast();
        }
        deque.offerLast(i);
        if (i >= k - 1) {
            result[i - k + 1] = nums[deque.peekFirst()];
        }
    }
    return result;
}

Notice how both ends do real work here. The front gives the current max. The back drops useless values before they pile up. No other single structure handles this as cleanly, which is why deques are a favorite in coding interviews.

▸ Interview Insight
Q: Can ArrayDeque be used as both a stack and a queue? How?
A: Yes, and that is its main selling point. For a stack, use push, pop, and peek, which all work on the head for LIFO order. For a queue, use offer to add at the tail and poll to remove from the head for FIFO order. One class covers both because it supports adding and removing at either end.

7. How ArrayDeque Works Inside

You do not need this to use the class. But knowing the internals helps you reason about speed and answer interview questions with confidence. Let us peek under the hood.

7.1 A Circular Array

Inside, ArrayDeque holds a plain array plus two markers: a head index and a tail index. The clever bit is that the array is treated as circular. When an index runs off the end, it wraps back to position zero.

Why bother with wrapping? It avoids shifting elements. In a normal array, removing from the front means sliding everything left. That is slow. With a circular array, you just move the head marker forward. Nothing shifts.

7.2 Why Both Ends Are Fast

Because of the two markers, adding or removing at either end is a quick pointer update. No element has to move. This is why front and back operations both run in constant time on average, written as O(1).

Compare that to an ArrayList. Removing from the front of an ArrayList shifts every remaining element left by one, which is O(n). ArrayDeque skips all that work, which is why it is the better choice for front-heavy operations.

7.3 Resizing When Full

The array has a fixed size at any moment. When it fills up, ArrayDeque allocates a new array, usually double the size, and copies the elements over. The default starting capacity is 16.

This copy costs O(n), but it happens rarely. Spread across many adds, the average cost per add stays O(1). This averaged view is called amortized constant time, and it is the same trick ArrayList uses.

7.3.1 A Note on Starting Capacity

If you know roughly how many items you will hold, tell ArrayDeque up front. Passing a size to the constructor cuts down on resize copies. There is one quirk worth knowing though. ArrayDeque rounds its capacity up to a power of two.

Why powers of two? It makes the circular index math cheaper. Wrapping an index around the array becomes a simple bitwise AND instead of a slower modulo. It is a small trick that adds up when you do millions of operations.

7.4 Iteration Order

When you loop over an ArrayDeque, it walks from head to tail. So it visits elements in the order a queue would serve them. There is also a descendingIterator() if you want to walk the other way, from tail to head.

One caution here. Do not change the deque while iterating over it, other than through the iterator itself. Doing so can throw a ConcurrentModificationException. This is the same rule you already follow with ArrayList and other collections.

Operation Time complexity Note
addFirst / addLast O(1) amortized Occasional resize copy
removeFirst / removeLast O(1) Just a pointer move
peekFirst / peekLast O(1) Direct index read
contains(e) O(n) Scans the whole deque
remove(Object) O(n) Must find it first

Table 2: Time complexity of common ArrayDeque operations.

8. ArrayDeque vs Other Choices

You have a few ways to build stacks and queues in Java. Let us line them up so you know which to reach for and why.

8.1 ArrayDeque vs Stack

The old Stack class extends Vector, so every method is synchronized. That lock slows you down when you do not need thread safety, which is most of the time. Stack also exposes index-based methods that break the clean stack idea.

ArrayDeque has no lock and a tighter design. For single-threaded stack work, it is the clear winner. Reach for it instead of Stack in new code.

8.2 ArrayDeque vs LinkedList

Both can act as a deque. The difference is what sits underneath. LinkedList uses nodes joined by pointers. ArrayDeque uses one contiguous array.

The array design gives ArrayDeque better speed and a smaller memory footprint for typical use. LinkedList only pulls ahead in rare cases, like when you need to insert in the middle using a list iterator. For plain stack or queue duty, prefer ArrayDeque.

The memory gap is bigger than people expect. Each LinkedList node is a separate object holding a value and two pointers. That is real overhead per element. ArrayDeque packs values into one flat array, so it wastes far less space and stays kind to the CPU cache.

8.3 ArrayDeque vs PriorityQueue

These solve different problems, so do not confuse them. ArrayDeque keeps insertion order at the ends. PriorityQueue instead serves elements by priority, smallest first by default, no matter when they went in.

Pick ArrayDeque when order of arrival matters. Pick PriorityQueue when you always want the most important item next, like the shortest job or the highest score.

Class Backed by Thread-safe Best for
ArrayDeque Resizable array No Fast stacks and queues
Stack Vector (array) Yes Legacy code only
LinkedList Doubly linked nodes No Middle inserts via iterator
PriorityQueue Binary heap No Serving by priority

Table 3: A quick comparison to help you choose.

8.4 A Simple Rule of Thumb

You do not have to memorize all of this. Here is a short rule that covers almost every case you will meet.

  • Need a stack or queue in one thread? Reach for ArrayDeque.
  • Need priority ordering? Reach for PriorityQueue.
  • Need many middle inserts via an iterator? Reach for LinkedList.
  • Need thread safety? Reach for a concurrent deque, not ArrayDeque.

Follow that and you will pick the right class most of the time. When in doubt for a plain stack or queue, ArrayDeque is the safe default.

▸ Interview Insight
Q: When would you choose LinkedList over ArrayDeque for a queue?
A: Rarely. ArrayDeque is faster and lighter for most stack and queue work because its array layout is cache-friendly. LinkedList makes sense only for frequent inserts or removals in the middle of the sequence using a ListIterator. Node relinking there avoids shifting. For pure front-and-back queue duty, ArrayDeque is the better pick almost every time.

9. Common Mistakes and Pitfalls

ArrayDeque is friendly, but a few traps catch beginners. Watch out for these.

9.1 Adding null

ArrayDeque forbids null. Add one and you get a NullPointerException on the spot. If your data can hold nulls, filter them out first or pick a different structure.

9.2 Mixing Up push and offer Direction

This one trips people up. push() adds to the head, the same end pop() removes from. But offer() adds to the tail. So mixing push with poll can flip your ordering in surprising ways.

Keep your intent clear. For stack behavior, stick to push and pop. For queue behavior, stick to offer and poll. Do not blend the two vocabularies in the same use.

9.3 Expecting Thread Safety

ArrayDeque is not synchronized. If two threads touch it at once without care, your data can corrupt. For concurrent work, use ConcurrentLinkedDeque or LinkedBlockingDeque instead.

9.4 Using It for Random Access

ArrayDeque has no get(index) method. It is built for the ends, not the middle. If you need to grab element number five directly, use an ArrayList instead. Right tool, right job.

10. Where You Will See ArrayDeque in Real Code

This class is not just for textbooks. It quietly powers a lot of everyday code. Once you know its shape, you will spot it fast.

10.1 Algorithms and Traversals

Graph and tree algorithms lean on it hard. A breadth-first search uses it as a queue. A depth-first search uses it as a stack. Iterative tree walks often keep pending nodes in an ArrayDeque.

10.2 Undo and History Features

Text editors, drawing apps, and browsers all track history. Each action goes on a stack, and undo pops the last one. ArrayDeque is a natural fit for this pattern.

10.3 Buffers and Schedulers

Task schedulers and job buffers use it as a queue to hold pending work in order. Some advanced schedulers use both ends, adding local tasks at one end and stealing from the other.

11. Conclusion

Let us wrap up. ArrayDeque in Java is a double-ended queue backed by a resizable circular array. You can add and remove from both ends, and both run in fast constant time.

Its real strength is flexibility. Use one end and it is a stack. Use both ends the normal way and it is a queue. It beats the old Stack class on speed, and it beats LinkedList on memory and cache friendliness.

So here is the takeaway. When you need a stack or a queue in single-threaded code, make ArrayDeque your default. It is fast, clean, and built for exactly this job. Reach for it, and you will rarely look back.

12. Interview Questions on ArrayDeque

Q: What is ArrayDeque in Java?

A: ArrayDeque is a class in java.util that gives you a double-ended queue backed by a resizable array. You can add and remove elements from both the front and the back, both in constant time on average. It can act as a fast stack or a fast queue, which makes it one class that does two jobs.

Q: Why is ArrayDeque preferred over the Stack class?

A: The Stack class extends Vector, so every method is synchronized. That adds locking overhead you rarely need in single-threaded code. ArrayDeque has no such lock and uses a faster array-based design. The Java documentation itself recommends ArrayDeque as the modern replacement for Stack.

Q: Can ArrayDeque be used as both a stack and a queue?

A: Yes, and that is its main strength. For a stack, use push, pop, and peek, which all work on the head for LIFO order. For a queue, use offer to add at the tail and poll to remove from the head for FIFO order. It supports both because it allows adding and removing at either end.

Q: Why does ArrayDeque not allow null elements?

A: Methods like poll and peek return null to signal an empty deque. If null were also a valid element, you could never tell an empty deque apart from one holding a null. By banning null, ArrayDeque keeps that signal clear, and adding a null throws a NullPointerException.

Q: What is the time complexity of ArrayDeque operations?

A: Adding, removing, and peeking at either end run in O(1) amortized time, since they are just pointer moves on a circular array. An occasional resize copy is O(n) but happens rarely, so the average stays constant. Searching with contains or removing a specific object is O(n) because it must scan.

Q: Is ArrayDeque thread-safe?

A: No. ArrayDeque is not synchronized, so multiple threads changing it at once can corrupt the data. For concurrent work, use ConcurrentLinkedDeque or LinkedBlockingDeque instead. In single-threaded code, the lack of locking is exactly why ArrayDeque is faster.

Q: When should you choose LinkedList over ArrayDeque?

A: Rarely. ArrayDeque is faster and lighter for most stack and queue work because its array layout is cache-friendly. LinkedList only makes sense when you need frequent inserts or removals in the middle of the sequence using a ListIterator. For plain front-and-back queue duty, prefer ArrayDeque.

Further Reading

Leave a Comment