Java Notes
Complete guide to Java ArrayDeque — circular array implementation, resizing strategy, performance advantages over LinkedList and Stack, internal working, and practical usage patterns.
What is ArrayDeque?
java.util.ArrayDeque is the recommended implementation of both Deque and Queue interfaces. It uses a resizable circular array internally, providing O(1) amortized time for all deque operations.
public class ArrayDeque<E>
extends AbstractCollection<E>
implements Deque<E>, Cloneable, SerializableFrom the official Java documentation:
"This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue."
How Operations Work
addLast(E e)
addFirst(E e)
pollFirst()
pollLast()
Resizing (doubleCapacity)
When head == tail (array is full), the capacity doubles:
Example:
| Array | [E] [F] [G] [H] [A] [B] [C] [D] |
| Logical | A B C D E F G H |
| Array | [A] [B] [C] [D] [E] [F] [G] [H] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] |
Time Complexity
| Operation | Time Complexity | Notes |
|---|---|---|
| ----------- | :-: | --- |
addFirst(E e) | O(1) amortized | Occasional O(n) resize |
addLast(E e) | O(1) amortized | Occasional O(n) resize |
pollFirst() | O(1) | Always constant |
pollLast() | O(1) | Always constant |
peekFirst() | O(1) | Always constant |
peekLast() | O(1) | Always constant |
size() | O(1) | Computed from head/tail |
contains(Object) | O(n) | Linear scan |
remove(Object) | O(n) | Linear scan + shift |
| Iteration | O(n) | Sequential access |
Complete Example
Deque: [First, Middle, Last] PeekFirst: First PeekLast: Last PollFirst: First PollLast: Last Remaining: [Middle] Stack operations: Push A, B, C: [C, B, A] Pop: C Peek: B
Why ArrayDeque Over LinkedList?
1. Memory Efficiency
Per element overhead
- ArrayDeque: ~8 bytes (one reference in array)
- LinkedList: ~48 bytes (Node: prev + next + item + object header)
For 1 million elements
- ArrayDeque: ~8 MB + array overhead
- LinkedList: ~48 MB (6x more memory!)
2. Cache Performance
3. No Object Allocation Per Element
// ArrayDeque: just stores the reference in the existing array
deque.addLast(element); // No new object created (usually)
// LinkedList: creates a new Node object every time
list.addLast(element); // new Node<>(prev, element, next)4. Benchmark Results
Adding 10 million elements
ArrayDeque.addLast(): ~80ms
LinkedList.addLast(): ~250ms
Polling 10 million elements
ArrayDeque.pollFirst(): ~50ms
LinkedList.pollFirst(): ~150ms
Why ArrayDeque Over Stack?
| Feature | Stack (legacy) | ArrayDeque |
|---|---|---|
| Synchronized | Yes (overhead) | No (faster) |
| Exposes list methods | Yes (get, set, add at index) | No (proper encapsulation) |
| Null elements | Allowed | ❌ Not allowed |
| Growth | Doubles (Vector) | Doubles (more efficient) |
| Recommended | ❌ Legacy | ✅ Modern |
Constructors
// Default: initial capacity 16
ArrayDeque<String> d1 = new ArrayDeque<>();
// Custom initial capacity (rounded up to next power of 2)
ArrayDeque<String> d2 = new ArrayDeque<>(100);
// Internal capacity becomes 128 (next power of 2 > 100)
// From another collection
ArrayDeque<String> d3 = new ArrayDeque<>(Arrays.asList("A", "B", "C"));Tip: If you know the expected size, specify it to avoid resizing:
// For 1000 expected elements:
ArrayDeque<Event> events = new ArrayDeque<>(1024); // Power of 2 ≥ 1000Null Restriction
ArrayDeque<String> deque = new ArrayDeque<>();
deque.add(null); // NullPointerException!
// WHY? ArrayDeque uses null internally to detect empty slots
// pollFirst() returns null when empty — if null elements were allowed,
// you couldn't distinguish "empty" from "element is null"Thread Safety
ArrayDeque is NOT thread-safe:
// Option 1: ConcurrentLinkedDeque (non-blocking, unbounded)
Deque<String> concurrent = new ConcurrentLinkedDeque<>();
// Option 2: LinkedBlockingDeque (blocking, optionally bounded)
BlockingDeque<String> blocking = new LinkedBlockingDeque<>(100);
blocking.putFirst("item"); // Blocks if full
String item = blocking.takeLast(); // Blocks if empty
// Option 3: ArrayBlockingQueue (for Queue-only usage)
BlockingQueue<String> abq = new ArrayBlockingQueue<>(100);Practical Patterns
BFS Template
public static void bfs(Node start) {
Deque<Node> queue = new ArrayDeque<>();
Set<Node> visited = new HashSet<>();
queue.offer(start);
visited.add(start);
while (!queue.isEmpty()) {
Node current = queue.poll();
process(current);
for (Node neighbor : current.getNeighbors()) {
if (visited.add(neighbor)) {
queue.offer(neighbor);
}
}
}
}DFS Template (Iterative)
public static void dfs(Node start) {
Deque<Node> stack = new ArrayDeque<>();
Set<Node> visited = new HashSet<>();
stack.push(start);
while (!stack.isEmpty()) {
Node current = stack.pop();
if (!visited.add(current)) continue;
process(current);
for (Node neighbor : current.getNeighbors()) {
if (!visited.contains(neighbor)) {
stack.push(neighbor);
}
}
}
}Bounded Buffer
public class BoundedBuffer<T> {
private final ArrayDeque<T> buffer;
private final int maxSize;
public BoundedBuffer(int maxSize) {
this.buffer = new ArrayDeque<>(maxSize);
this.maxSize = maxSize;
}
public void add(T item) {
if (buffer.size() >= maxSize) {
buffer.pollFirst(); // Remove oldest
}
buffer.addLast(item);
}
public List<T> getAll() {
return new ArrayList<>(buffer);
}
}Common Mistakes
1. Adding Null
deque.offer(null); // NPE! Use a sentinel object instead if needed2. Using as List (No Random Access)
// ArrayDeque does NOT implement List — no get(index)!
// deque.get(3); // Compilation error!
// If you need index access, use ArrayList3. Confusing Push Direction
4. Not Pre-sizing When Size is Known
Interview Questions
Q1: How does ArrayDeque work internally?
A: It uses a resizable circular array. Two pointers (head and tail) track the front and back. Elements wrap around the array, so both addFirst and addLast are O(1). When full, it doubles the capacity.
Q2: Why can't ArrayDeque store null?
A: Because it uses null internally as a sentinel to detect empty slots. poll() returns null when empty, so allowing null elements would make it ambiguous.
Q3: Why is ArrayDeque faster than LinkedList for queue/stack operations?
A: Better cache locality (contiguous memory), no per-element object allocation (no Node objects), and lower memory overhead (~8 bytes vs ~48 bytes per element).
Q4: What is the initial capacity of ArrayDeque?
A: 16 elements (or the next power of 2 ≥ the specified initial capacity).
Q5: What happens when ArrayDeque is full?
A: It doubles its internal array capacity and copies elements to the new array in correct logical order (unwrapping the circular buffer).
Q6: Can ArrayDeque be used as both a stack and a queue?
A: Yes. As a stack: push/pop/peek (LIFO from the head). As a queue: offer/poll/peek (FIFO — add to tail, remove from head).
Q7: What is the thread-safe alternative to ArrayDeque?
A: ConcurrentLinkedDeque (non-blocking) or LinkedBlockingDeque (blocking). There is no ArrayBlockingDeque in the JDK.
Q8: Why is ArrayDeque preferred over Stack class?
A: (1) ArrayDeque is faster — no synchronization overhead. (2) Stack extends Vector, exposing List methods that violate LIFO semantics. (3) ArrayDeque properly restricts access to ends only. (4) ArrayDeque implements Deque interface for consistent API. (5) ArrayDeque uses a resizable circular array with better cache locality.
Q9: How does ArrayDeque's circular array work internally?
A: ArrayDeque maintains a head and tail index in a backing array. Elements are added/removed by moving these indices. When head or tail wraps around the array end, it continues from the opposite side (circular). This allows O(1) operations at both ends without shifting elements.
Q10: Why doesn't ArrayDeque allow null elements?
A: ArrayDeque uses null as a sentinel value internally to detect empty slots and distinguish between "no element" and "element present." If null elements were allowed, pollFirst() returning null would be ambiguous — does it mean "deque is empty" or "the element was null"?
Q11: What is the initial capacity and growth strategy of ArrayDeque?
A: Default initial capacity is 16 (must be power of 2). When full, capacity doubles. The power-of-2 constraint enables efficient modular arithmetic using bitwise AND ((head - 1) & (elements.length - 1)) instead of expensive modulo operation.
Q12: When should you use ArrayDeque vs LinkedList for queue operations?
A: Almost always prefer ArrayDeque. It has better cache locality (contiguous memory), lower memory overhead (no node objects), and faster operations. LinkedList only wins when you need: (1) null elements, (2) frequent removal from middle via iterator, (3) simultaneous List interface access.
Summary
| Aspect | ArrayDeque |
|---|---|
| Structure | Circular resizable array |
| All deque operations | O(1) amortized |
| Null elements | ❌ Not allowed |
| Thread-safe | No |
| Memory efficiency | Excellent (contiguous, minimal overhead) |
| Use as stack | ✅ Preferred over java.util.Stack |
| Use as queue | ✅ Preferred over LinkedList |
| Initial capacity | 16 (always power of 2) |
| Growth | Doubles |
| The go-to choice for | Stack, Queue, or Deque — ArrayDeque is the default |
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for ArrayDeque in Java.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, collections, framework, queue
Related Java Master Course Topics