Stacks

Last In, First Out (LIFO)

TOP --> | D | | C | | B | | A | +-----+

CS205 Data Structures

Use arrow keys or buttons to navigate • Press S to reveal steps

1 / 18

What is a Stack?

A stack is a collection of elements with a LIFO access policy:

  • The last element inserted is the first one removed
  • Access is restricted to one end called the top
  • You can only add or remove from the top

Analogy: Stack of Plates

Think of a spring-loaded plate dispenser in a cafeteria. You always take the top plate. When clean plates are added, they go on top. You never pull from the bottom.

Key Idea

LIFO = Last In, First Out. The most recently added element is always the next one to leave.

+-------+ TOP --> | E | <-- most recent +-------+ | D | +-------+ | C | +-------+ | B | +-------+ | A | <-- oldest +-------+ push(F) adds F above E pop() removes and returns E
Cafeteria Plate Dispenser: ___________ | plate 5 | <-- take this one | plate 4 | | plate 3 | | plate 2 | | plate 1 | |___________| | spring | +===========+
2 / 18

The Stack ADT (Abstract Data Type)

Core Operations

MethodDescriptionTime
push(e)Add element e to topO(1)
pop()Remove & return topO(1)
top() / peek()Return top without removingO(1)
isEmpty()Is the stack empty?O(1)
size()Number of elementsO(1)

Key Idea

Every operation is O(1) -- constant time. A stack does very little, but does it blazing fast.

Java Interface

public interface Stack<E> { // Add element to the top void push(E element); // Remove and return the top E pop(); // Return top without removing E top(); // Is the stack empty? boolean isEmpty(); // Number of elements int size(); }

Warning

pop() and top() on an empty stack should throw an EmptyStackException (or return null, depending on design).

3 / 18

Push Operation

Add an element to the top of the stack

Before & After: push(D)

BEFORE AFTER push(D) +-------+ TOP --> | D | <-- new! +-------+ +-------+ | C | <-- TOP | C | +-------+ +-------+ | B | | B | +-------+ +-------+ | A | | A | +-------+ +-------+ size: 3 size: 4
Step by step: 1. Create space at top 2. Place element D there 3. Update top pointer 4. Increment size

Array-Based Push

// t is the index of the top element // starts at -1 (empty stack) public void push(E element) { if (size() == data.length) throw new FullStackException(); t++; // advance top index data[t] = element; // store element }

Linked-List Push

public void push(E element) { Node<E> newest = new Node<>(element); newest.next = top; // link to old top top = newest; // update top size++; }

Key Idea

Push is always O(1). It never needs to shift or move existing elements.

4 / 18

Pop Operation

Remove and return the top element

Before & After: pop() returns D

BEFORE AFTER pop() +-------+ | D | <-- TOP +-------+ +-------+ removed | C | <-- TOP | C | --------> +-------+ +-------+ returns D | B | | B | +-------+ +-------+ | A | | A | +-------+ +-------+ returned: D size: 4 size: 3

Warning: Always Check isEmpty!

Calling pop() on an empty stack is a runtime error. Always guard with isEmpty() or handle the exception.

Array-Based Pop

public E pop() { if (isEmpty()) throw new EmptyStackException(); E answer = data[t]; // save top element data[t] = null; // help garbage collection t--; // shrink stack return answer; }

Linked-List Pop

public E pop() { if (isEmpty()) throw new EmptyStackException(); E answer = top.data; // save data top = top.next; // bypass old top size--; return answer; }

Key Idea

Pop is O(1). Setting data[t] = null in the array version avoids memory leaks by allowing garbage collection.

5 / 18

Peek / Top

Look at the top element without removing it

peek() returns D but the stack is UNCHANGED +-------+ TOP --> | D | <-- returned! +-------+ | C | +-------+ | B | +-------+ D is still | A | on top after +-------+ peek() size before: 4 size after: 4 (no change!)

Analogy

peek() is like looking at the top plate without picking it up. pop() is actually taking it.

Implementation

// Array-based public E top() { if (isEmpty()) throw new EmptyStackException(); return data[t]; // no modification } // Linked-list-based public E top() { if (isEmpty()) throw new EmptyStackException(); return top.data; // no modification }

Comparison: top() vs pop()

Aspecttop()pop()
Returns top?YesYes
Removes top?NoYes
Changes size?NoYes (-1)
Destructive?NoYes
6 / 18

Array-Based Implementation

Store elements in an array, track the top index

Array data[] with capacity N = 8 top index t = 3 (4 elements) Index: 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ data | "A" | "B" | "C" | "D" | | | | | +-----+-----+-----+-----+-----+-----+-----+-----+ ^ | t = 3 (top) push("E"): data[4] = "E"; t = 4; pop(): temp = data[3]; data[3] = null; t = 2;

Key Idea

The stack "grows right" in the array. Index 0 is the bottom, index t is the top. An empty stack has t = -1.

Complete Java Class

public class ArrayStack<E> implements Stack<E> { private E[] data; private int t = -1; // top index public ArrayStack(int capacity) { data = (E[]) new Object[capacity]; } public int size() { return t + 1; } public boolean isEmpty() { return t == -1; } public void push(E e) { if (size() == data.length) throw new FullStackException(); data[++t] = e; } public E pop() { if (isEmpty()) throw new EmptyStackException(); E ans = data[t]; data[t] = null; t--; return ans; } public E top() { if (isEmpty()) throw new EmptyStackException(); return data[t]; } }
7 / 18

Array-Based: When the Array is Full

What happens when we push onto a full array?

Option 1: Throw Exception

Simple but limiting. Caller must know the max size in advance.

Option 2: Dynamic Resizing (Doubling)

BEFORE push("E") -- array is FULL Index: 0 1 2 3 +-----+-----+-----+-----+ data | "A" | "B" | "C" | "D" | capacity = 4 +-----+-----+-----+-----+ t = 3 (FULL!) Step 1: Allocate new array of size 2N = 8 Step 2: Copy all elements over Step 3: Push "E" Index: 0 1 2 3 4 5 6 7 +-----+-----+-----+-----+-----+-----+-----+-----+ data | "A" | "B" | "C" | "D" | "E" | | | | +-----+-----+-----+-----+-----+-----+-----+-----+ ^ t = 4 capacity = 8

Amortized Analysis

Doubling seems expensive (O(n) copy), but it happens rarely.

Push # Cost Array Size ----- ------ ---------- 1 1 1 2 1+1=2 2 (resize 1->2, copy 1) 3 1+2=3 4 (resize 2->4, copy 2) 4 1 4 5 1+4=5 8 (resize 4->8, copy 4) 6 1 8 7 1 8 8 1 8 ... Total cost for n pushes: n + (1 + 2 + 4 + ... + n) = n + 2n - 1 = 3n - 1

Key Idea: Amortized O(1)

Total cost of n pushes is O(n), so each push is O(1) amortized. The occasional expensive resize is "paid for" by all the cheap pushes that preceded it.

Warning

Never grow by a constant (e.g. +10). That gives O(n) amortized per push. Always double for O(1) amortized.

8 / 18

Linked-List-Based Implementation

Push and pop at the head of a singly linked list

The stack as a linked list: (top of stack = head of list) top | v +---+---+ +---+---+ +---+---+ +---+---+ | D | *-+-> | C | *-+-> | B | *-+-> | A | / | +---+---+ +---+---+ +---+---+ +---+---+ push(E): insert new node at HEAD top | v +---+---+ +---+---+ +---+---+ | E | *-+-> | D | *-+-> | C | *-+-> ... +---+---+ +---+---+ +---+---+ pop(): remove HEAD node, return E top | v +---+---+ +---+---+ | D | *-+-> | C | *-+-> ... +---+---+ +---+---+

Complete Java Class

public class LinkedStack<E> implements Stack<E> { private SinglyLinkedList<E> list = new SinglyLinkedList<>(); public int size() { return list.size(); } public boolean isEmpty() { return list.isEmpty(); } public void push(E e) { list.addFirst(e); // O(1) } public E pop() { return list.removeFirst(); // O(1) } public E top() { return list.first(); // O(1) } }

Key Idea

Push/pop at the head of a singly linked list is O(1). No resizing needed. No wasted capacity. No fixed limit.

9 / 18

Array vs Linked List Implementation

Tradeoffs between the two approaches

Criterion Array-Based Linked-List-Based
push / pop O(1) amortized O(1) worst-case
Memory per element Low -- just the element Higher -- element + pointer
Wasted space Up to N unused slots None
Resize cost O(n) occasionally Never needed
Maximum size Fixed (unless dynamic) Limited only by memory
Cache performance Excellent -- contiguous Poor -- scattered nodes
Implementation Simpler Slightly more complex

When to Use Array

When you know the maximum size in advance, or need best cache performance. Most practical implementations use arrays (e.g., java.util.Stack).

When to Use Linked List

When the stack size is highly unpredictable, or you need guaranteed O(1) worst-case per operation (no amortized spikes).

10 / 18

Application: Parenthesis Matching

Check if ( { [ ] } ) is balanced using a stack

Algorithm

  • Scan left to right
  • Opening bracket ( [ { : push it
  • Closing bracket ) ] } : pop and check match
  • At end: stack must be empty

Trace: ( { [ ] } )

Char Action Stack (top on right) ---- ----------- -------------------- ( push '(' ( { push '{' ( { [ push '[' ( { [ ] pop '[', ( { match ']' with '['? YES! } pop '{', ( match '}' with '{'? YES! ) pop '(', (empty) match ')' with '('? YES! END stack empty BALANCED!

Stack State at Each Step (Vertical)

Step 1: '(' Step 2: '{' Step 3: '[' +-----+ +-----+ +-----+ | | | | | [ | <- top +-----+ +-----+ +-----+ | | | { | <- top | { | +-----+ +-----+ +-----+ | ( | <- top | ( | | ( | +-----+ +-----+ +-----+ Step 4: ']' Step 5: '}' Step 6: ')' pop '[' pop '{' pop '(' +-----+ +-----+ +-----+ | | | | | | +-----+ +-----+ +-----+ | { | <- top | | | | +-----+ +-----+ +-----+ | ( | | ( | <- top | | EMPTY +-----+ +-----+ +-----+ VALID!

Failing Example: ( [ ) ]

Char Action Stack ---- -------------- ----- ( push '(' ( [ push '[' ( [ ) pop '[', MISMATCH! match ')' with '['? NO! --> INVALID
11 / 18

Application: Evaluating Postfix Expressions

Evaluate 3 4 + 2 * using a stack

What is Postfix (Reverse Polish)?

Operators come after their operands. No parentheses needed!

InfixPostfix
3 + 43 4 +
(3 + 4) * 23 4 + 2 *
3 + 4 * 23 4 2 * +

Algorithm

  • Number: push it
  • Operator: pop two operands, compute, push result
  • Final answer is the last value on the stack

Trace: 3 4 + 2 *

Token Action Stack (vertical) 3 push 3 +---+ | 3 | +---+ 4 push 4 +---+ | 4 | +---+ | 3 | +---+ + pop 4, pop 3 +---+ push 3+4=7 | 7 | +---+ 2 push 2 +---+ | 2 | +---+ | 7 | +---+ * pop 2, pop 7 +----+ push 7*2=14 | 14 | +----+ Result: 14
12 / 18

Application: Infix to Postfix Conversion

Shunting-Yard Algorithm (Dijkstra)

Algorithm Rules

  • Number: output it immediately
  • ( : push onto operator stack
  • ) : pop and output until ( is found
  • Operator: while top of stack has higher or equal precedence, pop and output. Then push current operator.
Precedence: * / --> 2 (higher) + - --> 1 (lower)

Key Idea

The operator stack holds operators "waiting" for their right operand. Higher-precedence operators are flushed first, ensuring correct evaluation order.

Trace: 3 + 4 * 2

Token Op Stack Output ----- --------- -------- 3 (empty) 3 + + 3 4 + 3 4 * + * 3 4 (* has higher prec than +, so push) 2 + * 3 4 2 END pop all 3 4 2 * + * first, then + Result: 3 4 2 * +

Verify: 3 4 2 * +

3 push 3 4 push 4 2 push 2 * 4*2=8 push 8 + 3+8=11 push 11 Answer: 11 Same as 3 + (4*2) = 11 ✓
13 / 18

Application: Function Call Stack

How your computer manages function calls with a stack

How It Works

Every time a function is called, a new stack frame is pushed. When the function returns, its frame is popped.

int main() { int x = factorial(3); // call } int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); }

Key Idea

Recursion is just the call stack in action. Each recursive call pushes a frame. Deep recursion = tall stack.

Call Stack for factorial(3)

Step 1: call factorial(3) +---------------------+ | factorial(3) | <- top +---------------------+ | main() | +---------------------+ Step 2: call factorial(2) +---------------------+ | factorial(2) | <- top +---------------------+ | factorial(3) | +---------------------+ | main() | +---------------------+ Step 3: call factorial(1) +---------------------+ | factorial(1) | <- top +---------------------+ | factorial(2) | +---------------------+ | factorial(3) | +---------------------+ | main() | +---------------------+ Step 4: call factorial(0) returns 1 Now frames pop one by one: factorial(1) returns 1*1 = 1 factorial(2) returns 2*1 = 2 factorial(3) returns 3*2 = 6
14 / 18

Application: Undo / Redo

Two stacks power every editor's undo system

How It Works

  • Perform action: push onto Undo stack, clear Redo stack
  • Undo: pop from Undo, push onto Redo
  • Redo: pop from Redo, push onto Undo
UNDO STACK REDO STACK +----------+ +----------+ | bold | <-top | | +----------+ +----------+ | type "b" | | | +----------+ +----------+ | type "a" | | | +----------+ +----------+ User presses Ctrl+Z (UNDO): pop "bold" from Undo, push to Redo UNDO STACK REDO STACK +----------+ +----------+ | type "b" | <-top | bold | <-top +----------+ +----------+ | type "a" | | | +----------+ +----------+
User presses Ctrl+Z again (UNDO): pop "type b" from Undo, push to Redo UNDO STACK REDO STACK +----------+ +----------+ | type "a" | <-top | type "b" | <-top +----------+ +----------+ | | | bold | +----------+ +----------+ User presses Ctrl+Y (REDO): pop "type b" from Redo, push to Undo UNDO STACK REDO STACK +----------+ +----------+ | type "b" | <-top | bold | <-top +----------+ +----------+ | type "a" | | | +----------+ +----------+ User performs NEW action "italic": push to Undo, CLEAR Redo UNDO STACK REDO STACK +----------+ +----------+ | italic | <-top | | (cleared!) +----------+ +----------+ | type "b" | | | +----------+ +----------+ | type "a" | | | +----------+ +----------+

Warning

A new action clears the Redo stack. Once you do something new after undoing, you lose the redo history.

15 / 18

Application: Browser Back / Forward

Navigation history uses two stacks

How It Works

  • Visit new page: push current onto Back stack, clear Forward stack
  • Back button: push current onto Forward, pop Back to become current
  • Forward button: push current onto Back, pop Forward to become current
Visit: Google -> Reddit -> YouTube BACK STACK Current FORWARD STACK +----------+ +----------+ | Reddit | YouTube | | +----------+ +----------+ | Google | | | +----------+ +----------+ Press BACK: BACK STACK Current FORWARD STACK +----------+ +----------+ | Google | Reddit | YouTube | +----------+ +----------+
Press BACK again: BACK STACK Current FORWARD STACK +----------+ +----------+ | | Google | Reddit | +----------+ +----------+ | YouTube | +----------+ Press FORWARD: BACK STACK Current FORWARD STACK +----------+ +----------+ | Google | Reddit | YouTube | +----------+ +----------+ Visit NEW page (Twitter): BACK STACK Current FORWARD STACK +----------+ +----------+ | Reddit | Twitter | | (cleared!) +----------+ +----------+ | Google | | | +----------+ +----------+

Analogy

Same pattern as Undo/Redo! The Back stack is the Undo stack, the Forward stack is the Redo stack, and the current page is the document state.

16 / 18

Common Pitfalls

Mistakes to avoid when working with stacks

1. Popping an Empty Stack

The #1 stack bug. Always check isEmpty() before pop() or top().

// BAD E val = stack.pop(); // crash! // GOOD if (!stack.isEmpty()) { E val = stack.pop(); }

2. Stack Overflow

Deep recursion = deep call stack. Each call pushes a frame. Too deep and you get StackOverflowError.

// Infinite recursion! int bad(int n) { return bad(n - 1); // no base case! // StackOverflowError }
+-------------+ | bad(-9999) | <- BOOM! +-------------+ | bad(-9998) | +-------------+ | ... | +-------------+ | bad(0) | +-------------+ | bad(1) | +-------------+ | main() | +-------------+

3. Wrong Order in Pop

In postfix evaluation, operand order matters for - and /.

// For "5 3 -" b = stack.pop(); // 3 a = stack.pop(); // 5 // Correct: a - b = 5 - 3 = 2 // Wrong: b - a = 3 - 5 = -2

Remember

First popped = second operand. Second popped = first operand.

17 / 18

Summary & Cheat Sheet

Stack at a Glance

+------- STACK --------+ | | | LIFO: Last In, | | First Out | | | | +-------+ | | | top | <- push/ | | +-------+ pop | | | | here | | +-------+ | | | | | | +-------+ | | | bottom| | | +-------+ | +-----------------------+

Operations Cheat Sheet

OperationTimeNotes
push(e)O(1)*Add to top
pop()O(1)Remove & return top
top()O(1)Return top (no remove)
isEmpty()O(1)Check if empty
size()O(1)Element count

* O(1) amortized for dynamic array

Implementations

Array-Based

Simple, fast, great cache performance. Use when size is bounded or mostly known. Amortized O(1) with doubling.

Linked-List-Based

No size limit, guaranteed O(1) worst-case. Extra memory for node pointers.

Applications

ApplicationHow Stacks Help
Parenthesis matchingPush open, pop on close
Postfix evaluationPush nums, pop for ops
Infix to postfixOperator stack (shunting-yard)
Function callsCall stack / recursion
Undo / RedoTwo stacks
Browser historyBack & Forward stacks

One Sentence Summary

A stack is a restricted list where you can only touch the top -- and that restriction is what makes it powerful, fast, and useful everywhere.

18 / 18