ArrayDeque in Java: The Better Stack and Queue
-
Last Updated: August 13, 2026
-
By: javahandson
-
Series
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.
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.
Here is the road ahead:
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 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.
The real charm of a deque is how many shapes it takes. Restrict yourself to certain methods, and it becomes a specific data structure.
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.
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.
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.
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.
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.
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.
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.
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. |
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.
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.
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.
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.
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.
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.
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.
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.
For stack work, ArrayDeque gives you three friendly names that match the classic stack vocabulary.
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.
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.
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.
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.
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.
For queue behavior, you add at one end and remove from the other. The common calls are simple.
So items enter at the tail and leave from the head. That gives you fair, in-order processing. First come, first served.
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.
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.
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. |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
You do not have to memorize all of this. Here is a short rule that covers almost every case you will meet.
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. |
ArrayDeque is friendly, but a few traps catch beginners. Watch out for these.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.