skip to content

Why is ArrayDeque recommended over the legacy java.util.Stack class for stack behavior?

level: middleimportance: should knowfreq 60%

answer

  1. Stack extends Vector -> synchronized + index/middle operations leak in
  2. Stack iteration is bottom-to-top (surprising); abstraction is broken
  3. ArrayDeque = circular array, unsynchronized, O(1) both ends, good locality
  4. JDK Javadoc explicitly recommends ArrayDeque over Stack and over LinkedList
  5. Concurrent need -> ConcurrentLinkedDeque/BlockingDeque, NOT Stack

basics

~10 s

Stack is old and extends Vector, so every operation is synchronized (slow) and it exposes index-based methods that break stack semantics. ArrayDeque is faster, unsynchronized, and gives a clean push/pop/peek stack API.

solid answer

~50 s

java.util.Stack is a legacy class from Java 1.0 that extends Vector. That inheritance is its core flaw: every method is synchronized, adding lock overhead even in single-threaded code, and because it IS-A Vector it inherits index-based and middle-of-list operations (get(i), add(i, e), insertElementAt) that violate the LIFO abstraction a stack is supposed to enforce. Its iteration order is also bottom-to-top, the opposite of what most people expect when 'iterating a stack'. ArrayDeque, introduced in Java 6, implements Deque on a resizable circular array. Used as a stack via push/pop/peek it is faster (no synchronization, better memory locality than a linked structure), exposes only deque operations, and the Java documentation explicitly recommends it over Stack. The one caveat: ArrayDeque is not thread-safe, so for genuine concurrent use you'd reach for a concurrent structure (e.g. ConcurrentLinkedDeque or a BlockingDeque), not Stack either.

code

java · 11 lines
java
// Legacy: synchronized, leaks Vector operations
Stack<Integer> legacy = new Stack<>();
legacy.push(1);
legacy.add(0, 99);   // index op from Vector -> breaks LIFO abstraction!

// Preferred: clean, fast, unsynchronized stack
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
int top = stack.pop();   // 2, LIFO
// stack.get(0);         // does not compile - no index access

go deeper

for a junior

Knows ArrayDeque is the modern, faster choice and Stack is old; can use push/pop/peek.

for a middle

Explains that Stack extends Vector (synchronized, leaks index operations, odd iteration order) and that ArrayDeque is an unsynchronized circular array.

for a senior

Discusses performance (locality, amortized O(1), lock overhead), the JDK recommendation, the null difference, and that Stack's synchronization is insufficient anyway.

for a principal

Frames it as an abstraction/inheritance design lesson (Stack IS-A Vector is bad LSP/composition), and prescribes the right concurrent alternatives (ConcurrentLinkedDeque/BlockingDeque) for real concurrency needs.

## The two classes **`java.util.Stack<E>`** is a class from Java 1.0 that models a LIFO stack with `push`, `pop`, `peek`, `empty`, and `search`. **`java.util.ArrayDeque<E>`** is a Java 6 class implementing the `Deque` interface; used as a stack it offers `push`, `pop`, and `peek`. Modern Java guidance and the JDK Javadoc itself recommend `ArrayDeque` over `Stack`. ## Why Stack is considered legacy ### 1. It extends Vector `Stack extends Vector`. `Vector` is a Java 1.0 resizable array whose every public method is **`synchronized`** (it acquires the object's monitor lock on each call). Consequences: - **Unnecessary locking overhead.** Even in single-threaded code you pay for synchronization on every `push`/`pop`. - **Broken abstraction.** Because `Stack` IS-A `Vector`, it inherits operations that have nothing to do with a stack: `get(int)`, `add(int, E)`, `insertElementAt`, `remove(int)`, `set(int, E)`. A caller can reach into the middle of the 'stack' and mutate it, defeating the LIFO contract. A clean stack should expose only push/pop/peek. - **Surprising iteration order.** Iterating a `Stack` (it IS-A List) goes **bottom-to-top** (insertion order), which is the opposite of popping order. People expect to iterate from the top. ### 2. Synchronization is the wrong default Single-thread synchronization is pure cost with no benefit, and the per-method locking of `Vector`/`Stack` is *not* even sufficient for compound operations (check-then-act still needs external locking), so it gives a false sense of safety while charging for it. ## Why ArrayDeque is better `ArrayDeque` is backed by a **resizable circular array** (a single array with head/tail indices that wrap around). Benefits: - **No synchronization** — no per-operation lock overhead in the common single-threaded case. - **Good cache locality** — contiguous array storage beats the pointer-chasing of a linked structure; amortized O(1) push/pop at both ends. - **Clean API** — as a `Deque` it exposes only deque/stack operations; you cannot index into the middle, so the LIFO/FIFO abstraction holds. - **Versatile** — the same class is an efficient FIFO queue *and* a LIFO stack. - **Recommended by the JDK** — the `Deque` and `ArrayDeque` Javadoc explicitly say it is likely faster than `Stack` when used as a stack (and faster than `LinkedList` when used as a queue). ## The important caveat: thread safety `ArrayDeque` is **not** synchronized. If multiple threads mutate it without external synchronization the behavior is undefined. But the answer to 'I need a concurrent stack' is **not** to fall back to `Stack` — it is to use a purpose-built concurrent structure such as `java.util.concurrent.ConcurrentLinkedDeque` (lock-free) or a `BlockingDeque` implementation. `Stack`'s coarse synchronization is both slow and insufficient for real concurrent algorithms. ## nulls Like other deques, `ArrayDeque` **forbids null** elements (they would collide with the null-means-empty signal of `peek`/`poll`), throwing `NullPointerException`. `Stack`/`Vector` *do* allow null, another subtle behavioral difference. ## One-line summary Prefer `ArrayDeque` (clean, fast, unsynchronized) for single-threaded stacks and queues; use a `java.util.concurrent` deque when you genuinely need concurrency. Treat `Stack` (and raw `Vector`) as legacy.

  • If you genuinely need a thread-safe stack/deque, what should you use instead of Stack?
    A concurrent structure such as ConcurrentLinkedDeque (lock-free, non-blocking) or a BlockingDeque (e.g. LinkedBlockingDeque) when you need blocking/capacity semantics. Stack's Vector-level synchronization is slower and still insufficient for compound check-then-act operations.
  • What is the data structure behind ArrayDeque and why does it help performance?
    A resizable circular array with head/tail indices that wrap around. Contiguous storage gives good cache locality and amortized O(1) insertion/removal at both ends, beating the pointer-chasing of linked alternatives.

saying these in an interview costs you the question

  • Recommending Stack because it is 'thread-safe' (its locking is slow and insufficient for compound ops)
  • Saying ArrayDeque is thread-safe (it is not)
  • Claiming ArrayDeque allows null elements (it forbids them)
  • Not knowing Stack extends Vector, which is the root of the criticism

context