Stack
Stack is a classic Last-In, First-Out (LIFO) data structure — the last element you put in is the first one that comes back out. Java has had a Stack class since Java 1.0, and it remains useful for interview questions, undo/redo logic, expression parsing, and depth-first algorithms.
What is a Stack?
Think of a stack like a stack of plates: you always add to and remove from the top. In Java, java.util.Stack<E> extends Vector and adds five classic stack operations on top of it.
import java.util.Stack;
public class BasicStack {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("first");
stack.push("second");
stack.push("third");
System.out.println(stack.pop()); // removes top
System.out.println(stack.peek()); // reads top without removing
System.out.println(stack.isEmpty());
}
}
Output:
third
second
false
Core Stack Methods
| Method | Description | Returns |
|---|---|---|
push(E item) | Pushes an element onto the top | the element pushed |
pop() | Removes and returns the top element | top element |
peek() | Returns the top element without removing it | top element |
isEmpty() | Checks whether the stack has no elements | boolean |
search(Object o) | Returns 1-based position from the top (-1 if not found) | int |
Warning:
pop()andpeek()throwEmptyStackExceptionif the stack is empty. Always guard withisEmpty()or a try-catch when needed.
push() and pop()
import java.util.Stack;
public class PushPop {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
for (int i = 1; i <= 5; i++) {
stack.push(i);
}
System.out.println("Stack: " + stack);
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
}
}
Output:
Stack: [1, 2, 3, 4, 5]
5 4 3 2 1
Notice how elements come out in reverse order — the LIFO principle in action.
search()
search() counts positions from the top (1 = top, 2 = one below, and so on):
import java.util.Stack;
public class SearchDemo {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
stack.push("a");
stack.push("b");
stack.push("c"); // top
System.out.println(stack.search("c")); // 1
System.out.println(stack.search("a")); // 3
System.out.println(stack.search("z")); // -1 (not found)
}
}
Output:
1
3
-1
Practical Example — Balanced Parentheses Checker
A classic Stack use-case is checking whether brackets in a string are balanced:
import java.util.Stack;
public class BalancedBrackets {
public static boolean isBalanced(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (stack.isEmpty()) return false;
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == ']' && top != '[') ||
(c == '}' && top != '{')) {
return false;
}
}
}
return stack.isEmpty();
}
public static void main(String[] args) {
System.out.println(isBalanced("{[()]}")); // true
System.out.println(isBalanced("{[(])}")); // false
System.out.println(isBalanced("((()")); // false
}
}
Output:
true
false
false
Practical Example — Reverse a String
import java.util.Stack;
public class ReverseString {
public static String reverse(String input) {
Stack<Character> stack = new Stack<>();
for (char c : input.toCharArray()) {
stack.push(c);
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(reverse("hello")); // olleh
}
}
Output:
olleh
Under the Hood
Stack extends Vector, which means every operation is synchronized — protected by an intrinsic lock. That is thread-safe but slow under contention because even single-threaded code pays the lock cost.
Internally, Vector (and therefore Stack) stores elements in an Object[]. When the array fills up, the backing array doubles in size — just like ArrayList. The difference is that every Vector method acquires the monitor on this before touching the array.
This legacy design has a few consequences:
- Single-threaded code: You pay synchronization overhead you don’t need.
- Multi-threaded code: Compound operations like
if (!stack.isEmpty()) stack.pop()are still NOT atomic, so the synchronization gives a false sense of safety. - Inheritance misuse: Because
StackextendsVector, callers can callVectormethods likeadd(0, element)orremove(0)and insert/remove from the bottom — violating LIFO semantics silently.
For these reasons, Stack is considered a legacy class. The Java documentation itself recommends using Deque implementations instead.
Modern Alternative — Deque as a Stack
ArrayDeque is the preferred replacement for Stack in modern Java. It is unsynchronized (faster for single-threaded use), avoids the Vector inheritance pitfall, and implements the Deque interface which provides explicit stack methods.
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeAsStack {
public static void main(String[] args) {
Deque<String> stack = new ArrayDeque<>();
// push to top
stack.push("first");
stack.push("second");
stack.push("third");
System.out.println(stack.peek()); // third
System.out.println(stack.pop()); // third
System.out.println(stack.size()); // 2
}
}
Output:
third
third
2
Tip: Declare the variable as
Deque<T>(the interface), notArrayDeque<T>, so you can swap implementations without changing call sites.
Stack vs ArrayDeque — Quick Comparison
| Feature | Stack | ArrayDeque (as stack) |
|---|---|---|
| Thread-safe | Yes (synchronized) | No |
| Performance (single-thread) | Slower | Faster |
| LIFO guarantee | Can be broken via Vector API | Enforced via Deque API |
| Recommended for new code | No | Yes |
| Package | java.util | java.util |
For concurrent scenarios, prefer java.util.concurrent.LinkedBlockingDeque or manage your own locking with a ReentrantLock (see ReentrantLock).
When to Use Stack
Use Stack (or ArrayDeque) when you need:
- Undo / redo operations (each action pushed; undo pops).
- Depth-first search (DFS) traversal of graphs or trees.
- Expression evaluation — converting infix to postfix, evaluating RPN.
- Call stack simulation — mimicking recursion iteratively.
- Backtracking algorithms — maze solving, N-Queens, Sudoku.
Note: For new code, always prefer
ArrayDequeoverStack. ReserveStackfor legacy codebases or situations where its specific API is required.
Iterating Over a Stack
Because Stack extends Vector which extends AbstractList, you can iterate with an enhanced for-each loop or an Iterator. Iteration goes from bottom to top (index 0 first):
import java.util.Stack;
public class IterateStack {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
stack.push(30);
// Bottom to top (index order)
for (int val : stack) {
System.out.print(val + " ");
}
System.out.println();
// Top to bottom (LIFO order) — pop into a list or use a loop
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
}
}
Output:
10 20 30
30 20 10
Warning: Never call
stack.remove(index)orstack.add(index, element)on aStack— these are inheritedVectormethods that bypass LIFO semantics and can corrupt the logical order of your stack.
Related Topics
- Deque & ArrayDeque — the modern, faster alternative to Stack for LIFO and FIFO operations
- Vector — Stack’s parent class; understanding it explains Stack’s synchronization behavior
- Queue & PriorityQueue — the complementary FIFO data structure
- LinkedList — also implements Deque; useful when frequent insertions/removals are needed
- ArrayList — the resizable-array sibling; helpful for comparing internal storage
- Collections Framework — the big picture of all Java collection types