Loading learning content...
If push is how data enters a stack, pop is how it leaves. The pop operation removes and returns the topmost element—the most recently pushed item still on the stack. This simple action, repeated in various patterns, powers everything from undo systems to expression evaluation to function call management.
Pop is where the LIFO (Last-In, First-Out) principle manifests most clearly. When you pop, you're not choosing which element to remove; the stack has already decided. The most recent push determines the next pop's result. This constraint transforms what could be chaos into predictable, reversible operations.
By the end of this page, you will master the pop operation completely—understanding its semantics, its dual relationship with push, implementations in both array and linked list forms, its complexity characteristics, critical edge cases (especially the empty stack), and the patterns where pop is central to algorithmic solutions.
The pop operation performs two actions atomically:
After a pop, the stack shrinks by one element, and the element that was second from the top becomes the new top.
The inverse relationship with push:
Pop and push are inverse operations. If you push element X and then immediately pop, you get X back, and the stack returns to its state before the push. This reversibility is fundamental to stack semantics and enables use cases like undo operations and backtracking algorithms.
1234567891011
Initial state: [10, 20] ← 20 is on top ↑ top push(30): [10, 20, 30] ← 30 is now on top ↑ top pop(): [10, 20] ← Returns 30; stack restored to previous state ↑ top Observation: pop() undoes the effect of the previous push() The stack returns to exactly the state it had before the pushTracing a sequence of operations:
Let's trace through multiple push and pop operations to see how the stack evolves:
1234567891011121314151617
Operation Stack State Returns Notes─────────────────────────────────────────────────────────────initial [] - empty stackpush(A) [A] - A is toppush(B) [A, B] - B is toppush(C) [A, B, C] - C is toppop() [A, B] C C removed, B is now toppop() [A] B B removed, A is now toppush(D) [A, D] - D is toppush(E) [A, D, E] - E is toppop() [A, D] E E removed, D is now toppop() [A] D D removed, A is now toppop() [] A A removed, stack emptypop() [] ERROR Cannot pop empty stack! Key insight: Elements exit in reverse order of entry.A was first in, last out. C was last in, first out. LIFO in action.Unlike peek (which we'll cover later), pop is destructive—it removes the element from the stack. Once popped, an element is gone from the stack forever unless you push it back. This makes pop a 'commitment' operation: you're done with that element.
Like all well-designed operations, pop has a clear contract that specifies what must be true before calling it, what will be true after, and what guarantees it provides.
size() was n before pop, it's n - 1 after.| Aspect | Description |
|---|---|
| Input | None (pop takes no parameters) |
| Output | The element that was at the top of the stack |
| Side Effect | Removes the top element; decrements size |
| Time Guarantee | O(1) in all standard implementations |
| Space Guarantee | O(1) — no additional space needed |
| Failure Mode | Stack underflow if called on empty stack |
Stack overflow (too many pushes) and stack underflow (popping empty stack) are symmetric error conditions. Overflow relates to capacity limits; underflow relates to emptiness. Both must be handled, but underflow is more common in practice—it's easy to pop more times than you pushed.
In an array-based stack, pop retrieves the element at the current top index and decrements the index. The element isn't physically removed from the array (it may still occupy the memory slot), but it's logically removed—the stack's invariants no longer consider it part of the stack.
The key insight: We read the element at topIndex, then decrement topIndex. The order matters—we need the element before we logically remove it.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
class ArrayStack<T> { private items: T[]; private topIndex: number; private capacity: number; constructor(capacity: number = 1000) { this.items = new Array<T>(capacity); this.topIndex = -1; // -1 indicates empty stack this.capacity = capacity; } /** * Pop: Remove and return the top element * * Time Complexity: O(1) * Space Complexity: O(1) * * @throws Error if stack is empty (stack underflow) * @returns The element that was at the top */ pop(): T { // Precondition check: ensure stack is not empty if (this.topIndex < 0) { throw new Error("Stack Underflow: Cannot pop from an empty stack"); } // Retrieve the top element const poppedElement = this.items[this.topIndex]; // Optional: Clear the reference to help garbage collection // (especially important for object types) this.items[this.topIndex] = undefined as any; // Decrement top index — element is now logically removed this.topIndex--; // Return the popped element return poppedElement; } /** * Check if stack is empty */ isEmpty(): boolean { return this.topIndex < 0; } /** * Push for completeness */ push(element: T): void { if (this.topIndex >= this.capacity - 1) { throw new Error("Stack Overflow"); } this.topIndex++; this.items[this.topIndex] = element; }}Step-by-step execution of pop():
12345678910111213141516171819202122
Before pop(): items: [10, 20, 30, _, _, ...] (capacity 1000) topIndex: 2 ← 30 is the top element Step 1: Check precondition topIndex (2) >= 0? Yes, proceed. Step 2: Retrieve top element poppedElement = items[2] = 30 Step 3: Clear reference (optional but recommended) items[2] = undefined Step 4: Decrement topIndex topIndex = 1 Step 5: Return poppedElement (30) After pop(): items: [10, 20, undefined, _, _, ...] topIndex: 1 ← 20 is now the top returned: 30Setting the popped slot to undefined (or null) prevents memory leaks in garbage-collected languages. Without clearing, the array holds a reference to the popped object, preventing it from being garbage collected even though it's 'logically' gone from the stack. This is called 'loitering.'
In a linked list-based stack, pop removes the head node and promotes the second node to become the new head. This is a pointer manipulation operation that naturally frees the old head for garbage collection.
The key insight: The head node is the top. Pop reads the head's value, then advances the head pointer to head.next. The old head node becomes unreferenced and will be collected.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
class StackNode<T> { value: T; next: StackNode<T> | null; constructor(value: T) { this.value = value; this.next = null; }} class LinkedListStack<T> { private head: StackNode<T> | null; private count: number; constructor() { this.head = null; this.count = 0; } /** * Pop: Remove and return the top element * * Time Complexity: O(1) * Space Complexity: O(1) * * @throws Error if stack is empty (stack underflow) * @returns The element that was at the top */ pop(): T { // Precondition check: ensure stack is not empty if (this.head === null) { throw new Error("Stack Underflow: Cannot pop from an empty stack"); } // Retrieve the value from the head node const poppedValue = this.head.value; // Update head to point to the next node // This disconnects the old head, making it eligible for GC this.head = this.head.next; // Decrement the count this.count--; // Return the popped value return poppedValue; } /** * Check if stack is empty */ isEmpty(): boolean { return this.head === null; } /** * Push for completeness */ push(element: T): void { const newNode = new StackNode(element); newNode.next = this.head; this.head = newNode; this.count++; }}Visualizing linked list pop:
1234567891011121314151617181920212223242526
Before pop(): head → [30 | next] → [20 | next] → [10 | null] ↑ top Step 1: Check precondition head !== null? Yes, proceed. Step 2: Retrieve value from head poppedValue = head.value = 30 Step 3: Update head pointer head = head.next [30 | next] → [20 | next] → [10 | null] (orphaned) ↑ new head Step 4: Decrement count (3 → 2) Step 5: Return poppedValue (30) After pop(): head → [20 | next] → [10 | null] ↑ top (new) The node containing 30 is now unreferenced and will be garbage collected. Stack contains: 20 (top), 10 (bottom)In linked list implementations, pop naturally allows garbage collection of the removed node—no explicit cleanup needed. The moment head advances, the old node has no references pointing to it (assuming you didn't keep any external references) and becomes eligible for collection.
Pop is one of the most efficient operations in computer science—constant time in all standard implementations. This efficiency is what makes stacks so valuable for performance-critical applications.
| Implementation | Best Case | Worst Case | Average | Notes |
|---|---|---|---|---|
| Fixed Array | O(1) | O(1) | O(1) | Index decrement, array access |
| Dynamic Array | O(1) | O(1) | O(1) | No shrinking on individual pop |
| Linked List | O(1) | O(1) | O(1) | Pointer update only |
Why is pop always O(1)?
Pop operates only on the top element. It never needs to examine, shift, or reorganize other elements in the stack. Whether your stack has 10 elements or 10 million, pop performs the same constant number of operations:
No loops. No traversals. No searching. Just direct access and a pointer/index update.
Some dynamic array implementations shrink the array when it becomes too empty (e.g., below 25% capacity). This can make individual pop operations O(n) during shrinking. However, like resizing on push, this amortizes to O(1) over many operations. Many practical implementations don't shrink on pop to avoid this complexity.
| Implementation | Space Overhead | Notes |
|---|---|---|
| Fixed Array | O(1) | No new allocations; index update only |
| Dynamic Array | O(1) amortized | May free memory during shrinking |
| Linked List | O(1) | Node becomes eligible for GC; no explicit work |
The most critical edge case for pop is the empty stack. Attempting to pop from an empty stack is an error—there's nothing to return, and the operation cannot complete successfully. This is called stack underflow.
Why is this edge case so important?
Unlike push (where overflow is rare with dynamic arrays and abundant memory), underflow is extremely common in real code. It's easy to write a loop that pops 'one too many times' or to forget that a sequence of operations might empty the stack.
stack.popOr(defaultValue) returns the top if present, or the default if empty. Clear intent but may mask bugs.1234567891011121314151617181920212223242526272829303132333435363738
// Strategy 1: Throw exception (most common, recommended)pop(): T { if (this.isEmpty()) { throw new Error("Stack Underflow: Cannot pop from empty stack"); } return this.internalPop();} // Strategy 2: Return nullable (requires careful caller handling)popOrNull(): T | null { if (this.isEmpty()) { return null; // Caller MUST check for null } return this.internalPop();} // Strategy 3: Return Optional type (type-safe handling)popOptional(): { hasValue: true; value: T } | { hasValue: false } { if (this.isEmpty()) { return { hasValue: false }; } return { hasValue: true, value: this.internalPop() };} // Strategy 4: Return with fallbackpopOr(defaultValue: T): T { if (this.isEmpty()) { return defaultValue; // Caller provides fallback } return this.internalPop();} // Strategy 5: Caller always checks (defensive coding)// Usage:if (!stack.isEmpty()) { const value = stack.pop(); // Safe because we checked process(value);}Returning null or a default value on empty stack pop can mask bugs. Your stack appears to work, but it's actually returning incorrect data. The bug might only manifest much later, in a completely different part of the codebase, making it extremely hard to diagnose. Prefer explicit failures via exceptions.
Pop is the action that 'consumes' stack data. Understanding common pop patterns helps you implement stack-based algorithms correctly and recognize when you've made an error (like popping at the wrong time).
1234567891011121314151617181920212223242526272829303132333435363738394041
// Pattern 1: Matching pop (parentheses validation)function isValidParentheses(s: string): boolean { const stack: string[] = []; const pairs: Record<string, string> = {')': '(', ']': '[', '}': '{'}; for (const char of s) { if ('([{'.includes(char)) { stack.push(char); // Push openers } else if (')]}');.includes(char)) { // Matching pop: must match the most recently pushed opener if (stack.length === 0 || stack.pop() !== pairs[char]) { return false; } } } return stack.length === 0; // All openers must be matched} // Pattern 2: Exhaustive pop (drain the stack)function processAllRemaining(stack: Stack<Task>): void { while (!stack.isEmpty()) { const task = stack.pop(); // Pop everything task.finalize(); }} // Pattern 3: Conditional pop (monotonic stack)function nextGreaterElement(nums: number[]): number[] { const result = new Array(nums.length).fill(-1); const stack: number[] = []; // stores indices for (let i = 0; i < nums.length; i++) { // Pop while current element is greater than stack top while (stack.length > 0 && nums[i] > nums[stack[stack.length - 1]]) { const idx = stack.pop()!; result[idx] = nums[i]; // Current is the next greater } stack.push(i); } return result;}In most stack algorithms, there's a logical balance between pushes and pops. For bracket matching, every opener pushed should eventually be popped when its closer is found. Unbalanced pushes/pops usually indicate an error—either in the algorithm or in the input data.
We've examined pop from every angle—its semantics, its relationship with push, its implementations, and its role in algorithms. Here are the essential takeaways:
What's Next:
We've covered the two 'active' stack operations: push (adding) and pop (removing). Next, we'll explore peek—the 'passive' operation that lets you see the top element without modifying the stack. Understanding peek completes your knowledge of stack interaction patterns.
You now have comprehensive knowledge of the pop operation—its semantics, implementations, complexity, edge cases, and algorithmic patterns. Together with push, pop gives you the core tools for stack manipulation. Next: peek, the read-only view of the stack's top.