Linear

Stack Fundamentals

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle โ€” the most recently added element is always the first one removed. Every push and pop operates on the same end (the "top"), making both operations O(1). Stacks power function call management, undo systems, expression parsing, and depth-first search.

01
Section One ยท Foundation

What is Stack?

10 20 30 ▼ top

A stack restricts all insertions and deletions to one end โ€” the top. You can only add an element on top (push) or remove the element currently on top (pop). There is no way to access or modify elements in the middle without first removing everything above them. This constraint is what makes stacks powerful: they naturally track the most recent context โ€” the last function call, the last opened bracket, the last undo-able action.

A stack can be backed by either an array or a linked list:

Array-Backed

Dynamic Array

Uses an ArrayList or resizable array under the hood. The top is the last index. Push and pop simply increment/decrement a pointer.

  • Cache-friendly โ€” contiguous memory
  • O(1) amortized push (resize rare)
  • Java ArrayDeque, Python list
Linked-List-Backed

Node Chain

Uses a singly linked list where the head is the top. Push and pop are addFirst/removeFirst โ€” always O(1), no resize ever.

  • True O(1) worst-case push/pop
  • No wasted capacity
  • Extra pointer overhead per element
Analogy: A stack of plates in a cafeteria. You can only take the plate on top, and when you add a clean plate, it goes on top of the pile. You never pull a plate from the middle โ€” that would collapse everything. The last plate placed is always the first one taken.
Key Characteristics
All operations happen at the top
Push, pop, peek are all O(1)
LIFO ordering preserved
Most recent element always accessible first
No random access to middle elements
Must pop everything above to reach deeper items
Function calls use a call stack
Recursion, backtracking, DFS built on stacks
Parentheses / brackets nest like a stack
Expression validation & parsing use stacks
02
Section Two ยท Operations

Core Operations

Push
Adds a new element to the top of the stack, increasing its size by one.
Pseudocode
function push(stack, value): if stack.size == stack.capacity: resize(stack) // array-backed: double capacity stack.top += 1 stack[stack.top] = value // O(1) โ€” place at top
Push is O(1) amortized with arrays, O(1) worst-case with linked lists. Array-backed stacks occasionally need to resize (copy all elements to a larger array), but because capacity doubles each time, the amortized cost per push across n operations is still O(1). Linked-list-backed stacks never resize at all.
Pop
Removes and returns the element at the top of the stack, decreasing its size by one.
Pseudocode
function pop(stack): if stack.isEmpty(): throw "StackUnderflow" // nothing to remove value = stack[stack.top] // grab top element stack.top -= 1 // shrink stack โ€” O(1) return value
Always check isEmpty() before popping. Popping from an empty stack is the most common stack-related runtime error. In Java, ArrayDeque.pop() throws NoSuchElementException; in Python, list.pop() throws IndexError. Guard every pop with an emptiness check.
Peek (Top)
Returns the element at the top of the stack without removing it โ€” a read-only view of the most recent element.
Pseudocode
function peek(stack): if stack.isEmpty(): throw "StackEmpty" return stack[stack.top] // O(1) โ€” no mutation
Peek is essential for look-ahead logic. When solving problems like valid parentheses or monotonic stack, you frequently need to check the top element before deciding whether to push or pop. Peek avoids destructive reads โ€” you see the top without losing it.
Monotonic Stack Pattern
Maintains a stack where elements are always in sorted order (increasing or decreasing), enabling O(n) solutions to "next greater/smaller element" problems.
Pseudocode โ€” Next Greater Element
function nextGreater(arr): result = new Array(arr.length, -1) stack = [] // stores indices for i = 0 to arr.length - 1: while stack is not empty and arr[i] > arr[stack.top]: idx = stack.pop() result[idx] = arr[i] // arr[i] is the next greater stack.push(i) return result // O(n) โ€” each element pushed/popped once
Every element is pushed and popped at most once. Although there is a while loop inside the for loop, the total number of pop operations across the entire run is at most n โ€” so the overall complexity is O(n), not O(n²). This is the key insight behind monotonic stack problems.
Common Mistake: Using Java's Stack class. It extends Vector (synchronized, slow) and allows random access via get(i), breaking the stack abstraction. Always use ArrayDeque instead โ€” it's faster and enforces proper LIFO semantics with push(), pop(), and peek().
03
Section Three ยท Visuals

Diagrams & Memory Layout

Stack Structure โ€” Array vs Linked List
Two backing implementations โ€” same LIFO behaviour
ARRAY-BACKED 10 20 30 [0] [1] top=2 [3] [4] LINKED-LIST-BACKED 30 top 20 10 Both: push/pop at the top โ€” O(1)
Push & Pop Walkthrough
Push 40, then pop โ€” stack grows and shrinks at the top
BEFORE 10 20 top PUSH 40 10 20 40 ← new top POP → 40 10 20 top 40 Both push and pop: O(1) โ€” only the top pointer changes
Monotonic Stack โ€” Next Greater Element
Processing array [2, 1, 4, 3] โ€” stack tracks unresolved elements
INPUT 2 1 4 3 i=0: push 2 i=1: 1<2 → push 1 i=2: 4>1 → pop 1 (NGE=4); 4>2 → pop 2 (NGE=4); push 4 i=3: 3<4 → push 3 RESULT 4 4 -1 -1 Each element pushed/popped at most once → O(n) total
04
Section Four ยท Code

Java Quick Reference

The most common stack operations using Java's ArrayDeque โ€” the recommended stack implementation (avoid the legacy Stack class).

Java โ€” ArrayDeque as Stack
import java.util.*; Deque<Integer> stack = new ArrayDeque<>(); // O(1) โ€” push onto top stack.push(10); // [10] stack.push(20); // [20, 10] stack.push(30); // [30, 20, 10] // O(1) โ€” peek at top without removing int top = stack.peek(); // 30 // O(1) โ€” pop from top int val = stack.pop(); // 30 → stack is [20, 10] // O(1) โ€” check emptiness boolean empty = stack.isEmpty(); // false // O(1) โ€” size int sz = stack.size(); // 2
05
Section Five ยท Complexity

Time & Space Complexity

OperationBest CaseAverage CaseWorst CaseSpace
PushO(1)O(1) amortizedO(n) on resizeO(1)
PopO(1)O(1)O(1)O(1)
PeekO(1)O(1)O(1)O(1)
Search (contains)O(1)O(n)O(n)O(1)
Why these complexities? Push, pop, and peek all operate exclusively on the top element โ€” a single pointer increment, decrement, or read. No traversal is needed. The only exception is array resize on push, which copies all n elements to a new array โ€” but because capacity doubles each time, this happens so rarely that the amortized cost is still O(1). Searching requires scanning the entire stack since there is no index-based access.
Space Complexity Note

A stack of n elements uses O(n) space. Array-backed stacks may over-allocate by up to 2× capacity, while linked-list-backed stacks use exactly n nodes plus one pointer per node. Auxiliary space for push, pop, and peek is O(1). Note that recursion implicitly uses the call stack โ€” every recursive call adds a frame, so a recursive algorithm processing n elements uses O(n) stack space even if you don't explicitly create a stack object.

06
Section Six ยท Decision Guide

When to Use Stack

Use This When

  • You need LIFO ordering โ€” undo/redo systems, backtracking, DFS traversal, and expression evaluation all require processing the most recent item first.
  • Matching nested structures โ€” parentheses validation, HTML tag matching, and recursive descent parsing rely on stacks to track open/close pairs.
  • Tracking "most recent" state โ€” monotonic stack problems, browser back-button history, and function call frames all fit the stack model naturally.

Avoid When

  • You need FIFO ordering โ€” if the first item added should be processed first (e.g. task scheduling, BFS), use a Queue instead.
  • Random access is required โ€” stacks don't support get(i); if you need to access elements by index, use an array or deque.
Comparison vs Alternatives
StructureAccessInsertDeleteBest For
Stack ← this oneO(1) top onlyO(1) pushO(1) popLIFO processing, DFS, undo, parsing
QueueO(1) front onlyO(1) enqueueO(1) dequeueFIFO processing, BFS, scheduling
DequeO(1) both endsO(1) either endO(1) either endSliding window, both LIFO & FIFO
07
Section Seven ยท Interview Practice

LeetCode Problems

Stack interview problems almost always involve one of three patterns: matching pairs (bracket validation, nested structures), monotonic stack (next greater/smaller element, histogram areas), or simulation (evaluate expressions, decode strings). Recognising "LIFO" or "most recent" in the problem statement is the key clue.

  • Easy Valid Parentheses โ€” Push every open bracket; on a close bracket, check the top matches โ€” classic stack matching in O(n).
  • Easy Min Stack โ€” Maintain a parallel stack that tracks the current minimum โ€” O(1) getMin by peeking the auxiliary stack.
  • Medium Evaluate Reverse Polish Notation โ€” Push operands; on an operator, pop two, compute, push the result โ€” direct stack simulation.
  • Medium Daily Temperatures โ€” Monotonic decreasing stack of indices; when a warmer day appears, pop and record the gap โ€” O(n).
  • Hard Largest Rectangle in Histogram โ€” Monotonic increasing stack of bar indices; on a shorter bar, pop and calculate area using the previous index as left boundary โ€” O(n).
Interview Pattern: When a problem involves "next greater element", "matching brackets", "decode nested strings", or any scenario where you process the most recent item first, immediately think Stack. If you see "next greater/smaller", reach for a monotonic stack โ€” it turns an O(n²) brute force into O(n).