Java Tutorial
🔍

Java ArrayDeque

Java ArrayDeque

java.util.ArrayDeque<E> is the collection the Java documentation explicitly recommends over both java.util.Stack and java.util.LinkedList for all queue and stack use cases. It is a concrete implementation of the Deque interface backed by a resizable circular array. No per-element object allocation, no synchronisation overhead, no wasted traversals for head operations — just direct array index arithmetic at both ends.

Every time a senior developer reviews code and replaces new Stack<>() with new ArrayDeque<>(), this is why.

What Is Java ArrayDeque?

ArrayDeque<E> is a concrete class in java.util that implements Deque<E>. It stores elements in a single resizable Object[] array using a circular buffer with two integer pointers — head and tail — that wrap around the array boundaries. This structure gives O(1) amortised operations at both ends simultaneously without the pointer-chasing penalty of LinkedList.

The diagram below shows where ArrayDeque sits in the Collections hierarchy.

java.lang.Iterable<E>
    └── java.util.Collection<E>
            └── java.util.Queue<E>          ← FIFO contract
                    └── java.util.Deque<E>  ← double-ended: both-end access
                            ├── ArrayDeque  ← THIS CLASS: circular resizable array
                            └── LinkedList  ← doubly-linked nodes, also implements List

KEY FACTS:
  Package         : java.util
  Since           : Java 1.6
  Implements      : Deque<E>, Cloneable, Serializable
  Backing store   : Object[] with circular head/tail indices
  Null            : NOT allowed — NullPointerException on any null insertion
  Duplicates      : allowed
  Thread-safe     : NO — use LinkedBlockingDeque for concurrent access
  Default capacity: 16 elements (internal — not configurable in the same way as ArrayList)
  Resize strategy : approximately double, always a power of 2

Basic Overview — The Full ArrayDeque API

ArrayDeque exposes every method in the Deque interface plus a few additions. All operations at both ends are O(1) amortised.

ARRAYDEQUE COMPLETE METHOD REFERENCE:

  HEAD OPERATIONS (front of deque):
    addFirst(e)    / offerFirst(e)  ← insert at head (throws / returns false)
    removeFirst()  / pollFirst()    ← remove from head (throws / returns null)
    getFirst()     / peekFirst()    ← inspect head (throws / returns null)

  TAIL OPERATIONS (back of deque):
    addLast(e)     / offerLast(e)   ← insert at tail (throws / returns false)
    removeLast()   / pollLast()     ← remove from tail (throws / returns null)
    getLast()      / peekLast()     ← inspect tail (throws / returns null)

  STACK ALIASES (all operate on head):
    push(e)  = addFirst(e)
    pop()    = removeFirst()
    peek()   = peekFirst()

  QUEUE ALIASES (standard Queue contract):
    add(e)     = addLast(e)
    offer(e)   = offerLast(e)
    remove()   = removeFirst()
    poll()     = pollFirst()
    element()  = getFirst()
    peek()     = peekFirst()

  ITERATION:
    iterator()            ← head → tail (front to back)
    descendingIterator()  ← tail → head (back to front, unique to Deque)

  SIZE:
    size(), isEmpty(), clear(), contains(e), toArray()

PRODUCTION RULE:
  Always prefer the null-returning variants:
    offerFirst, offerLast, pollFirst, pollLast, peekFirst, peekLast
  These never throw on empty — empty deque is a normal operating condition.

When to Use ArrayDeque

USE ArrayDeque WHEN:
  1. A LIFO stack is needed — always prefer over java.util.Stack
     Stack.push()/pop() acquire a mutex on every call (synchronized from Vector)
     ArrayDeque.push()/pop() have zero synchronisation cost

  2. A FIFO queue is needed — prefer over LinkedList
     LinkedList allocates a Node object per element; ArrayDeque does not
     ArrayDeque's contiguous array has better CPU cache locality

  3. Both-end access is needed on the same collection
     — sliding window algorithms, palindrome check, monotone deque
     — work-stealing schedulers, undo-redo history with fixed-size window

  4. Memory and GC pressure matter
     — no per-element Node → smaller heap footprint
     — fewer short-lived objects for GC to collect

CHOOSE LinkedList (as Deque) ONLY WHEN:
  - Both Deque and List interfaces are needed on the same reference
  - get(index) or set(index, e) or subList() required alongside deque operations
  - Null elements must be stored (ArrayDeque rejects null; LinkedList allows it)

CHOOSE LinkedBlockingDeque WHEN:
  - Multiple producer/consumer threads share the deque
  - Blocking takeFirst()/takeLast() that wait on empty is needed

NEVER USE java.util.Stack IN NEW CODE:
  - Synchronized on every operation even in single-threaded programs
  - Inherits Vector's get(index)/set(index) which break stack encapsulation
  - Java documentation explicitly recommends ArrayDeque as the replacement

How ArrayDeque Works Internally

The Circular Array and Head/Tail Mechanics

The core insight is that ArrayDeque treats its internal array as a circle. head and tail are integer indices that advance on opposite sides — head moves backward on addFirst (or forward on pollFirst), tail moves forward on addLast.

INITIAL STATE (empty ArrayDeque, capacity 16):
  elements: [ null × 16 ]
  head = 0,  tail = 0

AFTER addLast("A"), addLast("B"), addLast("C"):
  elements: [ "A" | "B" | "C" | null | null | ... | null ]
               0     1     2     3     4        15
  head = 0,  tail = 3

AFTER addFirst("Z") — insert BEFORE head:
  head = (0 - 1) & 15 = 15       ← bitwise wrap to last slot
  elements[15] = "Z"
  elements: [ "A" | "B" | "C" | null | ... | null | "Z" ]
               0     1     2     3           14     15
  head = 15, tail = 3

  Logical deque order: Z, A, B, C
  (read from head=15 → wraps at 0 → continues to tail-1=2)

AFTER pollFirst() — remove "Z":
  result = elements[15] = "Z"
  elements[15] = null            ← clear slot (helps GC)
  head = (15 + 1) & 15 = 0
  head = 0, tail = 3

  Logical deque order: A, B, C

BITWISE MASK: (index) & (capacity - 1)
  Capacity is always a power of 2 (16, 32, 64...).
  capacity - 1 is all-ones in binary (e.g., 15 = 0b1111).
  Any index & mask naturally wraps around — faster than modulo.
  Example: (15 + 1) & 15 = 16 & 15 = 0  (wraps to front)
           (0 - 1)  & 15 = -1 & 15 = 15 (wraps to back)

Resize — When and How

ArrayDeque resizes when the circular buffer becomes full — that is, when head == tail after an insertion. The resize copies elements in logical order (head to end, then 0 to tail-1) into a new array of double the capacity.

RESIZE SCENARIO (capacity 8, all 8 slots occupied):

  Before resize:
  elements: [ "E" | "F" | "G" | "H" | "A" | "B" | "C" | "D" ]
               0     1     2     3     4     5     6     7
  head = 4,  tail = 4  (full — head == tail)

  Logical order: A, B, C, D, E, F, G, H
  (starting at head=4, wrapping around)

  addLast("I") triggers resize:
    1. Allocate new Object[16]
    2. Copy elements from head (index 4) to end of old array → [A,B,C,D]
    3. Copy elements from index 0 to tail-1 (index 3) → [E,F,G,H]
    4. Result: new[0..7] = [A,B,C,D,E,F,G,H]
    5. Insert "I" at new[8]
    6. head = 0, tail = 9

  After resize:
  elements: [ "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | null×7 ]
               0     1     2     3     4     5     6     7     8
  head = 0,  tail = 9

  AMORTISED COST: each element is copied at most once per doubling.
  Over n insertions, total copy work is 1 + 2 + 4 + ... + n = 2n.
  Amortised per insertion: O(1).

  CAPACITY IS ALWAYS A POWER OF 2:
  This is enforced by the constructor. If you pass capacity 10,
  ArrayDeque internally rounds up to 16. This makes the bitwise
  mask trick always work correctly.

Memory Layout Advantage Over LinkedList

MEMORY COMPARISON (100 String elements, 64-bit JVM compressed refs):

  ArrayDeque:
    One Object[] array: 100 × 4 bytes = ~400 bytes for references
    Array object header: 16 bytes
    Total overhead: ~416 bytes

  LinkedList:
    100 × Node objects, each:
      Object header: 16 bytes
      item reference: 4 bytes
      next reference: 4 bytes
      prev reference: 4 bytes
    Per-Node total: ~28 bytes × 100 = ~2800 bytes

  ArrayDeque overhead: ~416 bytes
  LinkedList overhead: ~2800 bytes  (6.7× more)

  CACHE LOCALITY:
    ArrayDeque elements occupy consecutive memory addresses.
    When the CPU loads elements[i], hardware prefetching loads
    elements[i+1], [i+2], etc. into the L1 cache automatically.

    LinkedList node references point to different heap locations.
    Each node.next dereference causes a cache miss if the Node
    was allocated at a different time and lives in a different
    cache line. At high throughput this difference is measurable.

Core Operations with Examples

All Six Safe Methods — Both Ends

Java
1// File: ArrayDequeOpsDemo.java 2 3import java.util.ArrayDeque; 4import java.util.Deque; 5 6public class ArrayDequeOpsDemo { 7 8 public static void main(String[] args) { 9 10 Deque<String> deque = new ArrayDeque<>(); 11 12 // Build from both ends to show circular behaviour 13 deque.offerLast("Centre"); 14 deque.offerFirst("Before-Centre"); 15 deque.offerLast("After-Centre"); 16 deque.offerFirst("Front"); 17 deque.offerLast("Back"); 18 System.out.println("After 5 inserts : " + deque); 19 System.out.println("Size : " + deque.size()); 20 21 // Inspect both ends without removing 22 System.out.println("\n=== Peek at both ends ==="); 23 System.out.println("peekFirst() : " + deque.peekFirst()); // Front 24 System.out.println("peekLast() : " + deque.peekLast()); // Back 25 System.out.println("Unchanged : " + deque); 26 27 // Remove from both ends 28 System.out.println("\n=== Poll from both ends ==="); 29 System.out.println("pollFirst() : " + deque.pollFirst()); // Front 30 System.out.println("pollLast() : " + deque.pollLast()); // Back 31 System.out.println("After polls : " + deque); 32 33 // Safe methods on empty deque — null, no exception 34 System.out.println("\n=== Emptying and safe boundary checks ==="); 35 deque.clear(); 36 System.out.println("isEmpty() : " + deque.isEmpty()); 37 System.out.println("pollFirst() : " + deque.pollFirst()); // null 38 System.out.println("peekLast() : " + deque.peekLast()); // null 39 40 // Throwing methods on empty deque 41 try { 42 deque.removeFirst(); 43 } catch (java.util.NoSuchElementException e) { 44 System.out.println("removeFirst(): throws NoSuchElementException"); 45 } 46 47 // contains() — O(n) linear scan 48 System.out.println("\n=== contains() — O(n) ==="); 49 Deque<Integer> numbers = new ArrayDeque<>(java.util.List.of(10, 20, 30, 40, 50)); 50 System.out.println("contains(30) : " + numbers.contains(30)); // true 51 System.out.println("contains(99) : " + numbers.contains(99)); // false 52 } 53}
Output:
After 5 inserts : [Front, Before-Centre, Centre, After-Centre, Back]
Size            : 5

=== Peek at both ends ===
peekFirst() : Front
peekLast()  : Back
Unchanged   : [Front, Before-Centre, Centre, After-Centre, Back]

=== Poll from both ends ===
pollFirst() : Front
pollLast()  : Back
After polls : [Before-Centre, Centre, After-Centre]

=== Emptying and safe boundary checks ===
isEmpty()   : true
pollFirst() : null
peekLast()  : null
removeFirst(): throws NoSuchElementException

=== contains() — O(n) ===
contains(30) : true
contains(99) : false

ArrayDeque as a Stack — Replacing java.util.Stack

Java
1// File: ArrayDequeStackDemo.java 2 3import java.util.ArrayDeque; 4import java.util.Deque; 5 6public class ArrayDequeStackDemo { 7 8 // Evaluate a simple postfix expression using ArrayDeque as a stack 9 static int evaluatePostfix(String expression) { 10 Deque<Integer> stack = new ArrayDeque<>(); 11 12 for (String token : expression.trim().split("\\s+")) { 13 switch (token) { 14 case "+" -> { int b = stack.pop(); int a = stack.pop(); stack.push(a + b); } 15 case "-" -> { int b = stack.pop(); int a = stack.pop(); stack.push(a - b); } 16 case "*" -> { int b = stack.pop(); int a = stack.pop(); stack.push(a * b); } 17 case "/" -> { int b = stack.pop(); int a = stack.pop(); stack.push(a / b); } 18 default -> stack.push(Integer.parseInt(token)); 19 } 20 } 21 return stack.pop(); // final result 22 } 23 24 public static void main(String[] args) { 25 26 // Stack operations — all operate on head (front) 27 Deque<String> callStack = new ArrayDeque<>(); 28 System.out.println("=== ArrayDeque as LIFO stack ==="); 29 callStack.push("main()"); 30 callStack.push("processOrder()"); 31 callStack.push("validatePayment()"); 32 callStack.push("callPaymentGateway()"); 33 System.out.println("Stack (top first) : " + callStack); 34 System.out.println("peek() : " + callStack.peek()); 35 36 System.out.println("\nUnwinding call stack:"); 37 while (!callStack.isEmpty()) { 38 System.out.println(" returning from: " + callStack.pop()); 39 } 40 41 System.out.println(); 42 43 // Postfix expression evaluation 44 System.out.println("=== Postfix expression evaluation ==="); 45 String[] exprs = { 46 "3 4 +", // 3 + 4 = 7 47 "10 2 8 * + 3 -", // 10 + (2 * 8) - 3 = 23 48 "5 1 2 + 4 * + 3 -" // 5 + ((1+2)*4) - 3 = 14 49 }; 50 for (String expr : exprs) { 51 System.out.printf(" %-25s = %d%n", expr, evaluatePostfix(expr)); 52 } 53 } 54}
Output:
=== ArrayDeque as LIFO stack ===
Stack (top first)  : [callPaymentGateway(), validatePayment(), processOrder(), main()]
peek()             : callPaymentGateway()

Unwinding call stack:
  returning from: callPaymentGateway()
  returning from: validatePayment()
  returning from: processOrder()
  returning from: main()

=== Postfix expression evaluation ===
  3 4 +                     = 7
  10 2 8 * + 3 -            = 23
  5 1 2 + 4 * + 3 -         = 14

ArrayDeque as a FIFO Queue

Java
1// File: ArrayDequeQueueDemo.java 2 3import java.util.ArrayDeque; 4import java.util.Queue; 5 6public class ArrayDequeQueueDemo { 7 8 record Request(String id, String endpoint, long timestampMs) { 9 @Override public String toString() { 10 return String.format("[%s] %s", id, endpoint); 11 } 12 } 13 14 public static void main(String[] args) { 15 16 // Declared as Queue — only FIFO methods are accessible 17 Queue<Request> requestQueue = new ArrayDeque<>(); 18 19 System.out.println("=== ArrayDeque as FIFO queue ==="); 20 requestQueue.offer(new Request("R001", "POST /api/orders", System.currentTimeMillis())); 21 requestQueue.offer(new Request("R002", "GET /api/products", System.currentTimeMillis())); 22 requestQueue.offer(new Request("R003", "PUT /api/cart/item", System.currentTimeMillis())); 23 requestQueue.offer(new Request("R004", "DELETE /api/session", System.currentTimeMillis())); 24 25 System.out.println("Queue size : " + requestQueue.size()); 26 System.out.println("peek() (head) : " + requestQueue.peek()); // R001 27 28 System.out.println("\nProcessing in FIFO order:"); 29 while (!requestQueue.isEmpty()) { 30 Request req = requestQueue.poll(); // always returns oldest 31 System.out.println(" Dispatched: " + req); 32 } 33 34 System.out.println("\nQueue empty: " + requestQueue.isEmpty()); 35 System.out.println("poll() on empty: " + requestQueue.poll()); // null — safe 36 } 37}
Output:
=== ArrayDeque as FIFO queue ===
Queue size     : 4
peek() (head)  : [R001] POST /api/orders

Processing in FIFO order:
  Dispatched: [R001] POST /api/orders
  Dispatched: [R002] GET  /api/products
  Dispatched: [R003] PUT  /api/cart/item
  Dispatched: [R004] DELETE /api/session

Queue empty: true
poll() on empty: null

Iterating ArrayDeque

Java
1// File: ArrayDequeIterationDemo.java 2 3import java.util.ArrayDeque; 4import java.util.Deque; 5import java.util.Iterator; 6 7public class ArrayDequeIterationDemo { 8 9 public static void main(String[] args) { 10 11 Deque<String> pages = new ArrayDeque<>(); 12 pages.offerLast("Home"); 13 pages.offerLast("Products"); 14 pages.offerLast("Cart"); 15 pages.offerLast("Checkout"); 16 pages.offerLast("Confirmation"); 17 18 // for-each iterates head → tail (front to back — insertion order) 19 System.out.print("for-each (head→tail): "); 20 for (String page : pages) { System.out.print(page + " "); } 21 System.out.println(); 22 23 // descendingIterator iterates tail → head (back to front) 24 System.out.print("descendingIterator : "); 25 Iterator<String> desc = pages.descendingIterator(); 26 while (desc.hasNext()) { System.out.print(desc.next() + " "); } 27 System.out.println(); 28 29 // Safe removal during iteration using iterator.remove() 30 Deque<Integer> scores = new ArrayDeque<>(java.util.List.of(88, 32, 75, 14, 91, 56, 44, 100)); 31 System.out.println("\nBefore filter: " + scores); 32 Iterator<Integer> it = scores.iterator(); 33 while (it.hasNext()) { 34 if (it.next() < 50) it.remove(); // safe — iterator manages modCount 35 } 36 System.out.println("After remove(<50): " + scores); 37 38 // removeIf — modern Java 8+ alternative 39 Deque<Integer> scores2 = new ArrayDeque<>(java.util.List.of(88, 32, 75, 14, 91, 56, 44, 100)); 40 scores2.removeIf(score -> score < 50); 41 System.out.println("removeIf(<50) : " + scores2); 42 } 43}
Output:
for-each (head→tail): Home Products Cart Checkout Confirmation
descendingIterator  : Confirmation Checkout Cart Products Home

Before filter: [88, 32, 75, 14, 91, 56, 44, 100]
After remove(<50): [88, 75, 91, 56, 100]
removeIf(<50)     : [88, 75, 91, 56, 100]

Real-World Example — PhonePe UPI Transaction History Buffer

A UPI payments app displays the last N transactions in the current session without hitting the database on every navigation. New transactions are appended to the tail. When the buffer is full, the oldest transaction is evicted from the head. The user can navigate forward (newest) or backward (oldest) through the window. ArrayDeque's O(1) both-end access handles all four operations.

Java
1// File: TransactionEntry.java 2 3public record TransactionEntry( 4 String txnId, 5 String merchant, 6 double amount, 7 String status) { 8 9 @Override 10 public String toString() { 11 return String.format("[%s] %-18s Rs.%7.2f %s", 12 txnId, merchant, amount, status); 13 } 14}
Java
1// File: TransactionHistoryBuffer.java 2 3import java.util.ArrayDeque; 4import java.util.Deque; 5import java.util.Iterator; 6 7public class TransactionHistoryBuffer { 8 9 private final int bufferSize; 10 private final Deque<TransactionEntry> buffer; 11 12 public TransactionHistoryBuffer(int bufferSize) { 13 this.bufferSize = bufferSize; 14 this.buffer = new ArrayDeque<>(bufferSize + 1); 15 } 16 17 // Record a new transaction — evict oldest if buffer is full 18 public void record(TransactionEntry txn) { 19 if (buffer.size() == bufferSize) { 20 TransactionEntry evicted = buffer.pollFirst(); // remove oldest (head) 21 System.out.printf(" EVICTED : %s%n", evicted); 22 } 23 buffer.offerLast(txn); // append newest to tail 24 System.out.printf(" RECORDED : %s%n", txn); 25 } 26 27 // Show transactions in display order: newest first 28 public void displayNewestFirst() { 29 System.out.println("=".repeat(62)); 30 System.out.printf(" TRANSACTION HISTORY (last %d)%n", buffer.size()); 31 System.out.println("=".repeat(62)); 32 Iterator<TransactionEntry> it = buffer.descendingIterator(); // tail→head 33 int rank = 1; 34 while (it.hasNext()) { 35 System.out.printf(" %2d. %s%n", rank++, it.next()); 36 } 37 System.out.println("=".repeat(62)); 38 } 39 40 // Show transactions in chronological order: oldest first 41 public void displayOldestFirst() { 42 System.out.println("=".repeat(62)); 43 System.out.printf(" CHRONOLOGICAL HISTORY (last %d)%n", buffer.size()); 44 System.out.println("=".repeat(62)); 45 int rank = 1; 46 for (TransactionEntry txn : buffer) { // head→tail = oldest first 47 System.out.printf(" %2d. %s%n", rank++, txn); 48 } 49 System.out.println("=".repeat(62)); 50 } 51 52 public TransactionEntry latestTransaction() { return buffer.peekLast(); } 53 public TransactionEntry oldestTransaction() { return buffer.peekFirst(); } 54 55 public static void main(String[] args) throws InterruptedException { 56 57 TransactionHistoryBuffer history = new TransactionHistoryBuffer(5); 58 59 System.out.println("--- Recording transactions ---"); 60 history.record(new TransactionEntry("TXN-001","Zomato", 249.0, "SUCCESS")); 61 history.record(new TransactionEntry("TXN-002","BookMyShow", 599.0, "SUCCESS")); 62 history.record(new TransactionEntry("TXN-003","Jio Recharge", 239.0, "SUCCESS")); 63 history.record(new TransactionEntry("TXN-004","Amazon", 1299.0, "PENDING")); 64 history.record(new TransactionEntry("TXN-005","Swiggy", 189.0, "SUCCESS")); 65 66 System.out.println(); 67 history.displayNewestFirst(); 68 69 System.out.println("\n--- New transaction — buffer full, TXN-001 evicted ---"); 70 history.record(new TransactionEntry("TXN-006","Flipkart", 2499.0, "SUCCESS")); 71 72 System.out.println(); 73 history.displayNewestFirst(); 74 System.out.println(); 75 history.displayOldestFirst(); 76 77 System.out.println("\n--- Quick access ---"); 78 System.out.println("Latest : " + history.latestTransaction()); 79 System.out.println("Oldest : " + history.oldestTransaction()); 80 } 81}
Output:
--- Recording transactions ---
  RECORDED  : [TXN-001]  Zomato             Rs.  249.00  SUCCESS
  RECORDED  : [TXN-002]  BookMyShow         Rs.  599.00  SUCCESS
  RECORDED  : [TXN-003]  Jio Recharge       Rs.  239.00  SUCCESS
  RECORDED  : [TXN-004]  Amazon             Rs. 1299.00  PENDING
  RECORDED  : [TXN-005]  Swiggy             Rs.  189.00  SUCCESS

==============================================================
  TRANSACTION HISTORY  (last 5)
==============================================================
   1. [TXN-005]  Swiggy             Rs.  189.00  SUCCESS
   2. [TXN-004]  Amazon             Rs. 1299.00  PENDING
   3. [TXN-003]  Jio Recharge       Rs.  239.00  SUCCESS
   4. [TXN-002]  BookMyShow         Rs.  599.00  SUCCESS
   5. [TXN-001]  Zomato             Rs.  249.00  SUCCESS
==============================================================

--- New transaction — buffer full, TXN-001 evicted ---
  EVICTED   : [TXN-001]  Zomato             Rs.  249.00  SUCCESS
  RECORDED  : [TXN-006]  Flipkart           Rs. 2499.00  SUCCESS

==============================================================
  TRANSACTION HISTORY  (last 5)
==============================================================
   1. [TXN-006]  Flipkart           Rs. 2499.00  SUCCESS
   2. [TXN-005]  Swiggy             Rs.  189.00  SUCCESS
   3. [TXN-004]  Amazon             Rs. 1299.00  PENDING
   4. [TXN-003]  Jio Recharge       Rs.  239.00  SUCCESS
   5. [TXN-002]  BookMyShow         Rs.  599.00  SUCCESS
==============================================================

==============================================================
  CHRONOLOGICAL HISTORY  (last 5)
==============================================================
   1. [TXN-002]  BookMyShow         Rs.  599.00  SUCCESS
   2. [TXN-003]  Jio Recharge       Rs.  239.00  SUCCESS
   3. [TXN-004]  Amazon             Rs. 1299.00  PENDING
   4. [TXN-005]  Swiggy             Rs.  189.00  SUCCESS
   5. [TXN-006]  Flipkart           Rs. 2499.00  SUCCESS
==============================================================

--- Quick access ---
Latest : [TXN-006]  Flipkart           Rs. 2499.00  SUCCESS
Oldest : [TXN-002]  BookMyShow         Rs.  599.00  SUCCESS

Performance Considerations

OperationArrayDequeLinkedListjava.util.Stack
addFirst / pushO(1) amortisedO(1)O(1) + lock
addLast / offerO(1) amortisedO(1)O(1) + lock
removeFirst / popO(1) amortisedO(1)O(1) + lock
removeLastO(1) amortisedO(1)O(n) + lock
peekFirst / peekO(1)O(1)O(1) + lock
peekLastO(1)O(1)N/A
contains(e)O(n)O(n)O(n) + lock
Resize / growthO(n) amortisedN/AO(n) amortised
Memory per element~4-8 bytes~28 bytes (Node)~4 bytes + overhead
Iteration speedFast (cache-friendly)Slower (pointer-chased)Moderate

Stack.removeLast is O(n): java.util.Stack inherits Vector's structure — there is no tail pointer. Removing the last element requires traversal. ArrayDeque.removeLast() is O(1) via the tail index. This is a genuine asymptotic difference, not just a constant-factor improvement.

Resize is O(n) amortised: Each element is copied at most once per doubling cycle. Over n insertions, total copy work across all resizes is bounded by 2n. The amortised cost per insertion is O(1). This is the same guarantee that makes ArrayList append O(1) amortised.

Thread safety: ArrayDeque is not thread-safe. For concurrent producer-consumer with both-ends access, use LinkedBlockingDeque from java.util.concurrent. For single-producer single-consumer FIFO, ArrayBlockingQueue is more memory-efficient.

Best Practices

Declare the variable as Deque<E> for both-ends use, Queue<E> for FIFO-only use. Deque<String> history = new ArrayDeque<>() exposes the full both-ends API. Queue<String> tasks = new ArrayDeque<>() restricts callers to FIFO semantics only, which prevents accidental use of addFirst() when the intent is FIFO. Choose the narrowest interface that matches the actual access pattern.

Pre-size ArrayDeque when the expected number of elements is known. new ArrayDeque<>(expectedSize) allocates a backing array large enough to hold expectedSize elements without triggering any resize. The internal capacity is rounded up to the next power of 2. This eliminates O(n) resize operations and the temporary double-allocation during growth for bulk-loaded deques.

Use push()/pop()/peek() for stack semantics, offer()/poll() for queue semantics. The naming signals intent to readers. A method that uses push and pop reads as a stack. A method that uses offer and poll reads as a queue. Mixing them in one method (e.g., push to enqueue and pollLast to dequeue from the wrong end) produces correct but confusing code that is hard to maintain.

Clear slots after removal — ArrayDeque already does this for you. When pollFirst() or pollLast() removes an element, ArrayDeque nulls the vacated array slot. This is essential for garbage collection — if the slot were not cleared, the Object[] would hold a strong reference to the removed element even though logically it has been removed. This is handled internally and is one reason ArrayDeque is preferred over manually managed circular arrays.

Common Mistakes

Mistake 1 — Inserting null Into ArrayDeque

Java
1Deque<String> events = new ArrayDeque<>(); 2events.offerLast("LoginEvent"); 3 4// WRONG — ArrayDeque.offerFirst(null) throws NullPointerException 5// events.offerFirst(null); // NullPointerException 6 7// Why null is prohibited: pollFirst() returns null to signal empty deque. 8// If null elements were stored, the caller cannot distinguish between 9// "got a null element" and "deque was empty — no element returned". 10 11// CORRECT — use a non-null sentinel value or Optional<E> as element type 12events.offerFirst("SHUTDOWN"); // sentinel for consumer shutdown signal 13java.util.Optional<String> opt = java.util.Optional.empty(); 14// OR declare: Deque<Optional<String>> for nullable business values

Mistake 2 — Using java.util.Stack in New Code

Java
1// WRONG — every push/pop is synchronised, even on a single thread 2java.util.Stack<String> frames = new java.util.Stack<>(); 3frames.push("frameA"); // acquires mutex 4frames.push("frameB"); // acquires mutex 5String top = frames.pop(); // acquires mutex — pure overhead in single-threaded code 6 7// WRONG — Stack exposes Vector methods that make no sense for a stack 8frames.elementAt(0); // random access violates stack encapsulation 9frames.insertElementAt("frameX", 1); // breaks LIFO ordering 10 11// CORRECT — ArrayDeque as stack — zero synchronisation, correct encapsulation 12Deque<String> frames2 = new ArrayDeque<>(); 13frames2.push("frameA"); 14frames2.push("frameB"); 15String top2 = frames2.pop(); // O(1) amortised, no lock

Mistake 3 — Confusing Iteration Order With Priority Order

Java
1// ArrayDeque iterates in INSERTION order (head → tail), NOT value order 2Deque<Integer> nums = new ArrayDeque<>(); 3nums.offerLast(30); nums.offerLast(10); nums.offerLast(50); nums.offerLast(20); 4 5System.out.println("for-each: " + nums); // [30, 10, 50, 20] — insertion order 6// NOT [10, 20, 30, 50] — ArrayDeque is not a sorted structure 7 8// If processing in value order is needed, use PriorityQueue 9java.util.Queue<Integer> prioritised = new java.util.PriorityQueue<>(); 10prioritised.offer(30); prioritised.offer(10); prioritised.offer(50); prioritised.offer(20); 11System.out.print("PriorityQueue poll: "); 12while (!prioritised.isEmpty()) System.out.print(prioritised.poll() + " "); // 10 20 30 50 13System.out.println();
Output:
for-each: [30, 10, 50, 20]
PriorityQueue poll: 10 20 30 50

Mistake 4 — Using LinkedList When ArrayDeque Handles the Use Case

Java
1// WRONG for pure stack/queue/deque use — allocates a Node per element 2java.util.LinkedList<String> history = new java.util.LinkedList<>(); 3history.addFirst("Page1"); 4history.addLast("Page2"); 5 6// CORRECT — ArrayDeque for all push/pop/offer/poll/addFirst/addLast use 7Deque<String> history2 = new ArrayDeque<>(); 8history2.addFirst("Page1"); 9history2.addLast("Page2"); 10 11// LinkedList as Deque earns its place ONLY when List methods are also needed 12// on the same variable: get(index), set(index, e), subList(from, to) 13// For pure head/tail operations, ArrayDeque is always faster and lighter.

Interview Questions

Q1. What is ArrayDeque in Java and what are its main advantages over LinkedList and Stack?

ArrayDeque<E> is a concrete Deque implementation backed by a resizable circular array. Every operation at both ends is O(1) amortised. Compared to LinkedList: no per-element Node allocation means less GC pressure, and the contiguous array layout gives better CPU cache performance. Compared to java.util.Stack: Stack extends Vector, which synchronises every push(), pop(), and peek() call even in single-threaded code — pure overhead. ArrayDeque has no synchronisation. The Java documentation explicitly recommends ArrayDeque as the preferred replacement for both.

Q2. How does ArrayDeque implement O(1) operations at both ends?

ArrayDeque maintains two integer indices — head (the front element) and tail (the next insertion slot at the back) — into a circular Object[] array. addFirst(e) decrements head using bitwise wrap: head = (head - 1) & (capacity - 1), then writes elements[head] = e. addLast(e) writes elements[tail] = e, then increments tail. pollFirst() reads elements[head], nulls the slot, and increments head. All are direct array index operations — O(1). Resize happens when the array is full: a new array of double capacity is allocated, elements are copied in logical order, and head is reset to 0. This is O(n) but amortised O(1) over n insertions.

Q3. Why is the capacity of ArrayDeque always a power of 2?

The circular wrap-around uses bitwise AND masking: nextIndex = (index + 1) & (capacity - 1). This only produces correct wrap-around results when capacity - 1 is all-ones in binary — which happens precisely when capacity is a power of 2. For example, capacity 16 gives mask 15 (binary 1111). (15 + 1) & 15 = 16 & 15 = 0 — correctly wraps to the start. Bitwise AND is faster than modulo division, which is why Java enforces power-of-2 capacities internally. If you pass capacity 10 to the constructor, ArrayDeque rounds up to 16.

Q4. What is the difference between the throwing and null-returning method groups in ArrayDeque?

Every operation exists in two forms. The throwing group — addFirst, removeFirst, getFirst (and equivalents for last) — throws NoSuchElementException when the deque is empty, or IllegalStateException when capacity is exceeded (for bounded implementations). The null-returning group — offerFirst, pollFirst, peekFirst — returns null or false instead. For ArrayDeque, which is unbounded, capacity overflow never occurs, so the only practical difference is empty-deque behaviour. Production code should use the null-returning group for routine processing loops and reserve the throwing group for situations where an empty deque is a genuine programming error.

Q5. Can ArrayDeque be used as both a stack and a queue on the same object?

Yes — this is one of ArrayDeque's most useful properties. Both access patterns use opposite ends of the same circular buffer. Stack operations (push/pop) operate on the head. Queue operations (offer/poll) add to the tail and remove from the head. A sliding window buffer uses offerLast for new arrivals and pollFirst for eviction. All three patterns can coexist in the same ArrayDeque object without conflict, as long as the caller is consistent about which end represents the "front" for each semantic.

Q6. Why does ArrayDeque prohibit null elements?

pollFirst() and pollLast() return null to signal that the deque is empty — there is no separate isEmpty() check required before polling. If null elements were stored, a null return from poll would be ambiguous: it could mean "the deque was empty" or "the front element was null." Prohibiting null removes that ambiguity entirely. LinkedList allows null elements because it uses size > 0 as its emptiness signal rather than relying on the returned value. If a null-value sentinel is genuinely needed, use Optional<E> as the element type.

FAQs

What is the initial capacity of ArrayDeque and how does it grow?

The default internal capacity is 16 elements. The constructor new ArrayDeque<>(n) pre-allocates an array large enough for n elements, rounded up to the next power of 2. When the array becomes full, ArrayDeque allocates a new array of approximately double the capacity, copies all elements in logical order (head to tail), and resets head to 0. Growth is always to the next power of 2 to maintain the bitwise-mask wrap-around property.

Is ArrayDeque the same as a circular buffer?

Functionally yes. A circular buffer is any fixed-size buffer where reads and writes wrap around from the end back to the beginning. ArrayDeque uses exactly this structure with resizing on top. The key practical difference: a true fixed-size circular buffer has a maximum capacity and overwrites or rejects new elements when full. ArrayDeque never rejects elements — it resizes instead. For a true fixed-capacity circular buffer that overwrites old data, you would need to implement the eviction policy manually using ArrayDeque with a size guard, as shown in the real-world example above.

When should I use ArrayDeque over PriorityQueue?

Use ArrayDeque when processing order is either FIFO (arrival order) or LIFO (most-recently-added first). Use PriorityQueue when processing order depends on element value or priority — the smallest (or custom-highest-priority) element should always be processed next. ArrayDeque provides O(1) both-ends access; PriorityQueue provides O(log n) poll() and O(1) peek(). If your task queue needs priority-based dispatch, PriorityQueue is the right choice. If tasks should be processed in the order they arrived, ArrayDeque is faster.

Can ArrayDeque be iterated safely while removing elements?

Removing elements during for-each iteration throws ConcurrentModificationException — same as ArrayList and HashSet. Use iterator().remove() or removeIf(predicate) for safe removal during traversal. Both maintain modCount consistency internally. removeIf() is the cleaner Java 8+ choice for predicate-based removal and handles the bookkeeping transparently.

How does ArrayDeque compare to ArrayBlockingQueue for queue use?

Both use array-backed circular buffers, but for different scenarios. ArrayDeque is single-threaded, unbounded (grows as needed), and O(1) amortised. ArrayBlockingQueue is thread-safe, bounded (fixed capacity at construction), and uses a ReentrantLock for thread safety. ArrayBlockingQueue.put() blocks when full, take() blocks when empty — ideal for bounded producer-consumer pipelines. ArrayDeque is the choice for single-threaded workloads; ArrayBlockingQueue for concurrent ones.

Does ArrayDeque support random access by index?

No. ArrayDeque implements Deque not List, so there is no get(int index) method. It does not expose the internal array index — only the head and tail operations are public. If index-based access alongside deque operations is needed, LinkedList is the only standard Deque implementation that also implements List. For most cases where you think you need index access on a deque, you likely need either an ArrayList or a different design.

Summary

ArrayDeque<E> is the practical workhorse behind most Java stack and queue patterns. Its circular resizable array gives O(1) amortised operations at both ends with no per-element allocation overhead, no synchronisation cost, and cache-friendly sequential memory. The Java documentation recommends it explicitly over java.util.Stack and java.util.LinkedList for all non-concurrent queue and stack use cases.

Two implementation details that matter for interviews: capacity is always a power of 2 so the bitwise-AND wrap-around works correctly, and null elements are prohibited because poll() uses null as the "empty deque" signal. Both are design decisions made deliberately for performance and correctness.

Declare the variable as Deque<E> for both-ends code, Queue<E> for FIFO-only code. Pre-size when the expected element count is known. Never insert null. And when you review a PR that uses new Stack<>(), you now have the exact technical argument for why new ArrayDeque<>() is the right replacement.

What to Read Next

TopicLink
How the Deque interface defines all 12 method pairs that ArrayDeque implementsJava Collections Framework →
How LinkedList implements Deque differently and when it is the right choiceJava LinkedList →
How ArrayList's resizable array compares to ArrayDeque's circular buffer designJava ArrayList →
How the Iterator interface enables safe traversal and removal on ArrayDequeJava Iterator →
How Java Streams work with ArrayDeque elements for filter and collect operationsJava Streams API →
Java ArrayDeque | DevStackFlow