Java

Java Stack: Prefer ArrayDeque Over Stack

In Java, the clean default for stack-style code is Deque with ArrayDeque. It is faster to read, avoids the old synchronized Stack API, and works well for coding practice, parsers, monotonic stacks, and small in-memory workflows.

The Short Rule

SituationUse
Single-threaded stack or queueArrayDeque
Multi-threaded blocking handoffLinkedBlockingDeque
Multi-threaded non-blocking dequeConcurrentLinkedDeque
Legacy code onlyStack

For most algorithm problems, use:

Deque<Integer> stack = new ArrayDeque<>();

Then use stack operations:

stack.push(10);
stack.push(20);

int top = stack.peek(); // 20
int value = stack.pop(); // 20

Why Not Stack

java.util.Stack is an older class built on top of Vector. Its methods are synchronized because it comes from an older collection design.

That synchronization is usually not useful in normal algorithm code:

Use Stack only when maintaining old code that already depends on it.

Why ArrayDeque

ArrayDeque was added in Java 6 and is the normal replacement for stack and queue use cases when you do not need thread safety.

It gives you both ends of a deque:

Deque<Character> stack = new ArrayDeque<>();

stack.push('a');      // add to front
stack.push('b');
stack.peek();         // b
stack.pop();          // b

It also works as a queue:

Deque<String> queue = new ArrayDeque<>();

queue.offerLast("first");
queue.offerLast("second");

String next = queue.pollFirst(); // first

That makes ArrayDeque useful across many patterns:

Single-Threaded Code

If the structure is owned by one method, one request, or one worker, use ArrayDeque.

import java.util.ArrayDeque;
import java.util.Deque;

class Example {
    boolean hasBalancedParentheses(String s) {
        Deque<Character> stack = new ArrayDeque<>();

        for (char c : s.toCharArray()) {
            if (c == '(') {
                stack.push(c);
            } else if (c == ')') {
                if (stack.isEmpty()) {
                    return false;
                }
                stack.pop();
            }
        }

        return stack.isEmpty();
    }
}

This is the common shape for coding interviews and LeetCode-style problems.

Multi-Threaded Code

If multiple threads share the deque, choose the concurrency behavior explicitly.

Use LinkedBlockingDeque when producers and consumers should wait:

BlockingDeque<Event> queue = new LinkedBlockingDeque<>();

queue.putLast(event);        // producer may block
Event next = queue.takeFirst(); // consumer waits if empty

Use ConcurrentLinkedDeque when operations should be non-blocking:

Deque<Event> deque = new ConcurrentLinkedDeque<>();

deque.offerLast(event);
Event next = deque.pollFirst();

The difference matters:

NeedBetter fit
Backpressure and waiting consumersLinkedBlockingDeque
Lock-free concurrent add/removeConcurrentLinkedDeque
Local algorithm stateArrayDeque

Virtual Threads And Pinning

Project Loom changed how Java developers think about blocking. Virtual threads make blocking code cheaper, so code can often stay direct instead of becoming callback-heavy.

The old warning was: be careful with blocking inside synchronized code, because it could pin a virtual thread to its carrier thread. That specific synchronized pinning issue was addressed in Java 24 by JEP 491.

Java 26 adds another related improvement: virtual threads no longer stay pinned while waiting for another thread to finish class initialization.

So the practical rule is not "never synchronize because of Loom." The better rule is:

ArrayDeque is still the best default stack choice, but the reason is mostly clarity and fit, not fear of synchronized in modern Java.

Quick Pattern

Use this import block for most stack problems:

import java.util.ArrayDeque;
import java.util.Deque;

Use this declaration:

Deque<Integer> stack = new ArrayDeque<>();

Use these operations:

ActionMethod
Pushstack.push(value)
Read topstack.peek()
Popstack.pop()
Check emptystack.isEmpty()

Takeaways

References