A stack is a Last-In, First-Out (LIFO) structure: the last thing you push is the first thing you pop. Think of a pile of plates or the browser’s back-history—new plates go on the top and you always remove from the top. Because only one end is visible, stacks are tiny to reason about and very fast to use.
The essential operations are straightforward. push(x) places a value on top of the stack. pop() removes and returns the value on top (and is undefined or throws an error if the stack is empty). peek() reads the top item without removing it, while isEmpty() and size() tell you if there’s anything to read and how many items are currently stored. These operations map directly onto many real tasks: undo/redo in editors, parsing and expression evaluation, and depth-first search all naturally use stacks.
Every function call creates a stack frame that stores parameters, locals, and a return address. New calls push a frame and become the active one; when a function returns, its frame is popped and control resumes in the caller. Recursion grows the call stack until a base case; then frames unwind in reverse order. A stack overflow occurs when too many frames are pushed (for example, in deep or unbounded recursion). Prefer iterative solutions when appropriate, or ensure base cases are reached quickly.
Operation | Array-backed | Linked-list-backed | Notes |
---|---|---|---|
push(x) | Amortized O(1) | O(1) | Array may occasionally resize → one O(n) copy |
pop() | O(1) | O(1) | Remove the top |
peek() | O(1) | O(1) | Read top element |
isEmpty() | O(1) | O(1) | Check size/head |
size() | O(1) | O(1) | Keep a counter |
Stacks have a tiny, easy API and provide O(1) operations, which makes them a great fit for undo/redo, parsing, and search. The limitation is that only the top is visible; array-backed stacks sometimes pay a resizing cost, while linked-list stacks add per-node memory overhead.
Best for everyday Python. Lists provide append and pop at the end in amortized O(1), plus easy peek with s[-1].
s = [] s.append(10) # push s.append(20) top = s[-1] # peek -> 20 x = s.pop() # pop -> 20 empty = (len(s) == 0) n = len(s)
Also O(1) for push/pop on the right; implemented in C and memory efficient under heavy churn.
from collections import deque s = deque() s.append("A") # push s.append("B") print(s[-1]) # peek -> "B" print(s.pop()) # pop -> "B" print(len(s))
Fixed-capacity example to show the core logic; raises on overflow/underflow.
class ArrayStack: def __init__(self, capacity=8): self._a = [None] * capacity self._n = 0 def push(self, x): if self._n == len(self._a): raise IndexError("stack full") self._a[self._n] = x self._n += 1 def pop(self): if self._n == 0: raise IndexError("stack empty") self._n -= 1 x = self._a[self._n] self._a[self._n] = None return x def peek(self): if self._n == 0: raise IndexError("stack empty") return self._a[self._n-1] def isEmpty(self): return self._n == 0 def size(self): return self._n
Modern, fast stack API: push, pop, peek, isEmpty, size. Prefer over legacy Stack.
import java.util.*; public class JStackDemo { public static void main(String[] args) { ArrayDeque<Integer> s = new ArrayDeque<>(); s.push(1); s.push(2); s.push(3); System.out.println(s.peek()); // 3 System.out.println(s.pop()); // 3 System.out.println(s.size()); // 2 } }
Shows how a stack can be built by hand; all operations O(1).
public class LinkedStack<T> { private static class Node<T> { T v; Node<T> next; Node(T v){ this.v=v; } } private Node<T> top; private int n = 0; public void push(T x){ Node<T> t = new Node<>(x); t.next = top; top = t; n++; } public T pop(){ if (n == 0) throw new java.util.NoSuchElementException(); T v = top.v; top = top.next; n--; return v; } public T peek(){ if (n == 0) throw new java.util.NoSuchElementException(); return top.v; } public boolean isEmpty(){ return n==0; } public int size(){ return n; } }
Older synchronized class; API is fine but ArrayDeque is the modern choice.
import java.util.Stack; Stack<String> s = new Stack<>(); s.push("x"); s.push("y"); System.out.println(s.peek()); System.out.println(s.pop());
Simple LIFO adapter over another container (defaults to std::deque<T>).
#include <stack> #include <iostream> int main(){ std::stack<int> s; s.push(10); s.push(20); s.push(30); std::cout << s.top() << "\n"; // 30 s.pop(); std::cout << s.size() << "\n"; // 2 }
When you want full control and no abstraction overhead.
#include <vector> #include <iostream> int main(){ std::vector<int> s; s.push_back(1); s.push_back(2); s.push_back(3); std::cout << s.back() << "\n"; // 3 s.pop_back(); std::cout << s.size() << "\n"; // 2 }
Answers: (1) Last-In, First-Out — the last pushed is the first popped. (2) 3. (3) The array may need to resize and copy elements when it runs out of capacity. (4) Each call pushes a new frame with its locals and return address; returns pop frames to resume the caller. (5) Too many frames exhaust the fixed stack space. (6) Examples include a pile of plates, browser back button history, or undo/redo in an editor.