
Shankar LWhy should you care? A stack is one of the simplest and most useful data structures in...
A stack is one of the simplest and most useful data structures in computer science. It organizes data according to one fundamental rule:
Last In, First Out (LIFO).
The last item added to the stack is the first item removed.
Stacks appear everywhere in programming:
If you've learned about the call stack, you're already using the idea of a stack—you just haven't necessarily implemented one yourself.
Suppose you have several tasks that need to be processed, but the most recently added task should be handled first.
For example:
Task A
Task B
Task C ← added last
You want:
C → B → A
An ordinary array doesn't automatically enforce this behavior.
We need a data structure that restricts how elements are added and removed so that:
The newest element always comes out first.
That's the problem a stack solves.
A stack is a linear data structure that follows the LIFO principle:
Last In
↓
First Out
Imagine:
┌─────┐
│ 30 │ ← TOP
├─────┤
│ 20 │
├─────┤
│ 10 │
└─────┘
If we remove an element, 30 comes out first.
Then:
┌─────┐
│ 20 │ ← TOP
├─────┤
│ 10 │
└─────┘
The two fundamental operations are:
Add an element to the top.
Before:
10 → 20
Push(30)
After:
10 → 20 → 30
↑
TOP
Remove the element from the top.
Before:
10 → 20 → 30
↑
TOP
Pop()
After:
10 → 20
↑
TOP
There is also another important operation:
Look at the top element without removing it.
10 → 20 → 30
↑
TOP
Peek() → 30
Think of a stack of plates.
You put plates on top:
Add
↓
┌───┐
│ 3 │
├───┤
│ 2 │
├───┤
│ 1 │
└───┘
If you want to take a plate, you naturally take the top plate.
You can't conveniently remove the bottom plate without first removing the plates above it.
So:
Push: 1 → 2 → 3
↑
TOP
Pop: 3 comes out first
That's Last In, First Out.
Imagine a pile of documents on your desk.
You place them one on top of another:
Document A
Document B
Document C
Document D ← newest
If you always pick up the top document first:
D → C → B → A
The last document you placed down is the first one you pick up.
That's exactly how a stack behaves.
A stack can be implemented using an array.
Here's a simple Java implementation:
class Stack {
int[] data;
int top = -1;
Stack(int size) {
data = new int[size];
}
void push(int value) {
data[++top] = value;
}
int pop() {
return data[top--];
}
int peek() {
return data[top];
}
}
We can use it like this:
Stack stack = new Stack(5);
stack.push(10);
stack.push(20);
stack.push(30);
System.out.println(stack.peek());
System.out.println(stack.pop());
System.out.println(stack.pop());
Output:
30
30
20
Let's follow what happened:
push(10)
[10]
↑
TOP
Then:
push(20)
[20] ← TOP
[10]
Then:
push(30)
[30] ← TOP
[20]
[10]
peek() gives:
30
but doesn't remove it.
pop() removes:
30
leaving:
[20] ← TOP
[10]
In modern Java, Deque is generally preferred for stack behavior:
import java.util.ArrayDeque;
import java.util.Deque;
Deque<Integer> stack = new ArrayDeque<>();
stack.push(10);
stack.push(20);
stack.push(30);
System.out.println(stack.pop());
Output:
30
A stack uses:
LIFO
Last In → First Out
A queue uses:
FIFO
First In → First Out
For example:
Stack:
10 → 20 → 30
pop() → 30
Whereas a queue would remove:
10
The difference is which end elements leave from.
peek() removes an element
These operations are different:
push() → add
pop() → remove
peek() → inspect
For example:
Stack:
10
20
30 ← TOP
After:
peek()
the stack remains:
10
20
30 ← TOP
After:
pop()
it becomes:
10
20 ← TOP
Consider:
Stack:
(empty)
Calling:
stack.pop();
is invalid.
This situation is called stack underflow.
Similarly, if you're implementing a fixed-size stack using an array and try to push when it is already full, you have stack overflow at the data-structure level.
Don't confuse this with the call-stack overflow that occurs when a program exhausts its call-stack memory, often because of excessive recursion.
A properly implemented stack provides:
| Operation | Time Complexity |
|---|---|
| Push | O(1) |
| Pop | O(1) |
| Peek | O(1) |
| Search | O(n) |
The key advantage is that stack operations happen at the top, so no shifting of elements is normally required.
An array implementation can track the top using an integer:
data:
┌────┬────┬────┬────┬────┐
│ 10 │ 20 │ 30 │ │ │
└────┴────┴────┴────┴────┘
↑
TOP
Here:
top = 2
A push increments top:
top = top + 1
A pop decrements it:
top = top - 1
This makes both operations constant time.
Stacks can also be implemented using linked lists.
TOP
↓
[30] → [20] → [10] → null
Push:
new node
↓
[40] → [30] → [20] → [10]
↑
TOP
Pop simply removes the first node.
This is efficient because inserting and removing from the beginning of a linked list are both O(1).
One of the most important uses of stacks is the function call stack.
Consider:
void main() {
functionA();
}
void functionA() {
functionB();
}
void functionB() {
System.out.println("Hello");
}
When the program runs:
main()
↓
functionA()
↓
functionB()
The calls are placed onto the call stack:
┌──────────────┐
│ functionB() │ ← TOP
├──────────────┤
│ functionA() │
├──────────────┤
│ main() │
└──────────────┘
When functionB() finishes, it is removed first.
Then:
functionA()
finishes.
Then:
main()
finishes.
That's LIFO in action.
Consider:
void count(int n) {
if (n == 0)
return;
System.out.println(n);
count(n - 1);
}
Calling:
count(3);
creates:
count(3)
↓
count(2)
↓
count(1)
↓
count(0)
The calls are placed onto the call stack.
When the base case is reached, the functions return in reverse order:
count(0)
↑
count(1)
↑
count(2)
↑
count(3)
This is why understanding stacks is essential for understanding recursion.
Every function call requires some amount of call-stack memory.
If recursion continues without reaching a base case:
void infinite() {
infinite();
}
the call stack keeps growing:
infinite()
infinite()
infinite()
infinite()
...
Eventually, available call-stack memory is exhausted.
The program can then produce a StackOverflowError in Java.
Stacks connect directly to several fundamental programming concepts:
Variables
↓
Memory
↓
Functions
↓
Call Stack
↓
Recursion
↓
Stacks
↓
DFS / Backtracking / Parsing
Stacks are also heavily used by algorithms.
A graph traversal can use a stack:
A
/ \
B C
/
D
DFS explores deeply before returning:
A → B → D → C
Stacks are also useful for evaluating expressions such as:
(10 + 20) × 30
and checking balanced parentheses:
{ [ ( ) ] }
For example:
(
(
( )
← matched
A stack can keep track of opening brackets and remove them when matching closing brackets appear.
A stack is a controlled pile where you can only interact with the top.
Remember these three operations:
TOP
↓
┌─────┐
PUSH → │ 30 │
├─────┤
│ 20 │
├─────┤
POP ← │ 10 │
└─────┘
PEEK → Look at 30 without removing it
The single rule to remember is:
LIFO
Last In → First Out
Once you understand this, many seemingly unrelated concepts—function calls, recursion, undo operations, DFS, and expression parsing—start looking like variations of the same idea.
A stack is a linear data structure that follows the Last In, First Out (LIFO) principle.
The key ideas are:
A stack is more than a data structure—it is the fundamental mechanism behind the way programs remember what they are currently doing and how they should return from it.