Java ArrayDeque Queue: Use It for FIFO Queues
java arraydeque queue: Learn how to use Java ArrayDeque as a queue, its performance advantages, and when it's a better choice than LinkedList.
If you need a single-ended FIFO queue in Java and you reach for LinkedList, you may be missing a more efficient option. The ArrayDeque class, introduced in Java 6, implements a resizable-array based double-ended queue and can serve perfectly well as a Queue implementation. In many cases, java arraydeque queue usage provides better performance and lower memory overhead than LinkedList for queue-like workloads.
This article focuses on the practical question: when should you use ArrayDeque as your queue, and how do you do it correctly? We'll examine the API, behavior, performance characteristics, and common pitfalls.
Queue Operations with ArrayDeque
ArrayDeque implements the Deque interface, which extends Queue. This means you can use it wherever a Queue is expected. The two primary operations are offer() for adding elements and poll() for retrieving and removing the head of the queue.
Here is a minimal example that creates a queue backed by ArrayDeque and processes a few tasks in FIFO order:
import java.util.ArrayDeque; import java.util.Queue; Queue<String> taskQueue = new ArrayDeque<>(); taskQueue.offer("compile"); taskQueue.offer("test"); taskQueue.offer("package"); String nextTask = taskQueue.poll(); // returns "compile" and removes it
In this example, offer() adds elements to the tail, and poll() retrieves and removes the head. If the queue is empty, poll() returns null, which is critical to handle in production code.
Unlike LinkedList, ArrayDeque does not allow null elements. Attempting to add a null will throw a NullPointerException. This is a deliberate constraint that enables more efficient internal representation. If you need to store null values, you'll need a different collection, such as LinkedList or an ArrayList-based queue.
Why ArrayDeque Is Often a Better Queue Choice
Many developers reach for LinkedList when they need a queue because it implements List and Deque. However, ArrayDeque has several advantages:
- Better memory locality: The elements are stored in a contiguous array, which improves cache performance during iteration.
- No node objects:
LinkedListcreates a separate node object for each element, adding overhead.ArrayDequestores elements directly in its internal array. - Faster traversal: Accessing elements in an array is generally faster than following pointers between nodes.
- No random access overhead:
LinkedListoffers O(n) access by index, but you shouldn't need that for a queue anyway.
For a single-threaded FIFO queue, ArrayDeque is almost always the better choice. The Oracle documentation explicitly notes that ArrayDeque is likely to be faster than Stack when used as a stack and faster than LinkedList when used as a queue.
Resizing Behavior and Capacity
The ArrayDeque class uses an internal circular array. When the array becomes full, it doubles its capacity. This resizing operation is O(n), but it happens infrequently enough that the amortized cost remains O(1) per operation.
You can pre-size the ArrayDeque if you know the expected number of elements. This avoids unnecessary resizing and can save memory. Use the constructor that accepts an initial capacity:
Queue<String> queue = new ArrayDeque<>(1000);
The initial capacity must be a positive integer. The implementation may round up to a power of two for internal alignment.
A common question is whether to set a large capacity to avoid resizing. While that can help performance, it can also waste memory if the queue never fills. A good practice is to choose a capacity that closely matches the expected workload without doubling unnecessarily.
Performance: Time and Memory Considerations
When you compare the runtime behavior of ArrayDeque and LinkedList, a few distinctions stand out. Both offer O(1) amortized time for offer() and poll(), but the constants differ.
- ArrayDeque: Uses a dynamic array. Accessing the element at a specific index is O(1). Adding or removing at the ends is amortized O(1). No per-element overhead beyond the array slot.
- LinkedList: Each element is a separate object containing the value and references to the previous and next elements. Adding or removing at the ends is O(1) without resizing, but each node consumes more memory (typically 24–40 bytes extra per element depending on JVM).
Memory usage is a concrete difference. For example, a LinkedList with 10,000 Integer objects will allocate 10,000 node objects plus the Integer objects. An ArrayDeque will allocate one object (the array) plus the Integer objects, with some wasted space due to resizing but usually much less than the node overhead.
Because the article does not contain benchmark data, we rely on the established algorithmic behavior. In practice, you'll notice the difference when you process millions of elements in performance-sensitive code.
Concurrency: ArrayDeque Is Not Thread-Safe
ArrayDeque is not synchronized. If you need a thread-safe queue, you have a few options:
ConcurrentLinkedQueue: A lock-free, thread-safe queue designed for high-concurrency scenarios.LinkedBlockingQueue: A blocking queue that can be used with the producer-consumer pattern.ArrayBlockingQueue: A bounded, blocking queue backed by an array.
You should not use ArrayDeque in a multi-threaded environment without external synchronization. Even with Collections.synchronizedQueue(), the iterator is not fail-fast safe for concurrent modification, and compound operations (like check-then-act) still require additional locking.
If you do use ArrayDeque across threads, you must synchronize on the queue object itself. A simple example:
Queue<String> queue = Collections.synchronizedQueue(new ArrayDeque<>()); // Still need to synchronize for compound actions synchronized (queue) { if (queue.peek() != null) { queue.poll(); } }
But for most cases, it's simpler and safer to use a dedicated concurrent queue.
Iteration and Bulk Operations
ArrayDeque supports iteration in both directions. When used as a queue, you might want to iterate over all pending tasks without removing them. The enhanced for-loop works, but you must be careful not to modify the queue during iteration unless you use an iterator's remove() method.
for (String task : queue) { System.out.println("Processing: " + task); // Do not call queue.remove() here directly }
If you need to iterate and remove, use the iterator's remove() method, which is safe for ArrayDeque (though it is not guaranteed to be fail-fast). However, note that ArrayDeque's iterator does not provide remove() because the Deque interface does not require it. The Queue interface doesn't define an iterator at all; you need to cast to Collection or Iterable to use the enhanced for-loop. Actually, Queue extends Collection, so iteration is available, but if you want to remove elements safely during iteration, you must call iterator.remove() which is implemented by ArrayDeque's iterator. Let's verify: yes, ArrayDeque provides an iterator that supports remove().
Here is an example of safely removing elements while iterating:
Iterator<String> iterator = queue.iterator(); while (iterator.hasNext()) { String task = iterator.next(); if (task.startsWith("skip:")) { iterator.remove(); } }
This is allowed because ArrayDeque's iterator implements the Iterator interface with a remove() method.
Common Mistakes and Edge Cases
One of the most frequent mistakes is using add() instead of offer(). Both methods add elements, but add() throws an exception if the queue is full (for bounded queues). For ArrayDeque, which is unbounded, add() will never throw an IllegalStateException due to capacity, but it's still better to use offer() because it communicates the possibility of failure, and it's the standard queue method.
Another edge case is using peek() and poll() simultaneously. peek() returns the head without removing it. If you call peek() and then poll(), you must handle the case where between the two calls the queue might become empty (in a concurrent scenario). Even in single-threaded code, it's easy to forget to check for null after poll() if you already checked peek().
Also, remember that ArrayDeque does not allow null. If you need to represent "no value" in a queue, you'll have to use a sentinel object or a preset value, or use a different collection.
When to Choose ArrayDeque vs. LinkedList or ConcurrentLinkedQueue
Use ArrayDeque when:
- You need a single-threaded FIFO queue.
- You need fast, predictable queue operations.
- You don't need to store null elements.
- You want to minimize memory overhead.
Use LinkedList when:
- You need to store null elements.
- You need a
Listand aDequesimultaneously. - You need to insert/remove elements in the middle (though that's rarely a queue use case).
Use ConcurrentLinkedQueue when:
- You need thread-safe queue operations without a hard bound.
- You are using a producer-consumer pattern and don't need blocking.
For blocking queues, use LinkedBlockingQueue or ArrayBlockingQueue. The choice depends on whether you need bounded capacity and whether you need fairness guarantees.
The bottom line is that for a typical FIFO queue in a single-threaded Java application, ArrayDeque is the best starting point. It combines low overhead, high throughput, and a clean API.