Stacks

Last-in, first-out collection for undo, parsing, and nested work.

Stacks Operations & Functions

Stack functions

Each pattern below is something you will reach for while solving stack problems. Skim the short description, then copy the snippets for common situations.

Prefer Overview for how stacks work, and Problems for practice once problems are linked.

Array as a stack

In TypeScript, a plain array is already a stack if you only push and pop at the end.

push() / pop()

Grow and shrink from the end — the classic LIFO pair.

array-stack.tsTypeScript
const stack: number[] = [];

// ——— push (top grows) ———
stack.push(7);
stack.push(3);
stack.push(9); // [7, 3, 9] · top is 9

// ——— pop (top leaves first) ———
const top = stack.pop(); // 9
// stack is [7, 3]

// ——— drain ———
while (stack.length > 0) {
  const v = stack.pop()!;
  // use v
}

length as emptiness

Treat length === 0 as isEmpty. Guard before every pop.

array-empty.tsTypeScript
function isEmpty(stack: unknown[]): boolean {
  return stack.length === 0;
}

function safePop<T>(stack: T[]): T | undefined {
  return stack.length === 0 ? undefined : stack.pop();
}

Custom Stack class

Wrap the array when you want named methods and a clear public API.

class Stack

Thin wrapper around an array with push, pop, peek, and size.

stack-class.tsTypeScript
class Stack<T> {
  private readonly items: T[] = [];

  push(value: T): void {
    this.items.push(value);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  get size(): number {
    return this.items.length;
  }

  get isEmpty(): boolean {
    return this.items.length === 0;
  }

  clear(): void {
    this.items.length = 0;
  }
}

const s = new Stack<number>();
s.push(1);
s.push(2);
s.peek(); // 2
s.pop(); // 2

Peek & contains

Read the top, or ask whether a value sits somewhere in the pile.

peek()

Look at the last index without calling pop.

peek.tsTypeScript
const stack = [7, 3, 9];

// ——— top without remove ———
const top = stack[stack.length - 1]; // 9
// stack unchanged

// ——— with a guard ———
function peek<T>(stack: T[]): T | undefined {
  return stack.length === 0 ? undefined : stack[stack.length - 1];
}

contains()

Scan from the top down, or use includes on the backing array.

contains.tsTypeScript
const stack = [7, 3, 9];

// ——— array helper ———
stack.includes(3); // true

// ——— walk from top (matches “search from top”) ———
function contains<T>(stack: T[], target: T): boolean {
  for (let i = stack.length - 1; i >= 0; i--) {
    if (stack[i] === target) return true;
  }
  return false;
}

Balanced parentheses

Push openers, pop on closers — the classic stack interview warm-up.

isBalanced()

Match brackets with a stack. Wrong closer or leftovers mean invalid.

balanced-parens.tsTypeScript
const pairs: Record<string, string> = {
  ')': '(',
  ']': '[',
  '}': '{',
};

function isBalanced(s: string): boolean {
  const stack: string[] = [];

  for (const ch of s) {
    if (ch === '(' || ch === '[' || ch === '{') {
      stack.push(ch);
      continue;
    }
    const open = pairs[ch];
    if (!open) continue; // ignore other chars
    if (stack.pop() !== open) return false;
  }

  return stack.length === 0;
}

isBalanced('([])'); // true
isBalanced('([)]'); // false

Undo stack

Keep a history of states or commands so you can rewind one step at a time.

undo history

Push snapshots (or actions) on change; pop to restore the previous value.

undo-stack.tsTypeScript
type Edit = { before: string; after: string };

const history: Edit[] = [];
let text = '';

function typeText(next: string): void {
  history.push({ before: text, after: next });
  text = next;
}

function undo(): void {
  const edit = history.pop();
  if (!edit) return;
  text = edit.before;
}

typeText('hi');
typeText('hi!');
undo(); // text is 'hi' again

redo with two stacks

Undo pops from history onto a redo stack; redo pushes back.

undo-redo.tsTypeScript
const undoStack: string[] = [];
const redoStack: string[] = [];
let value = '';

function setValue(next: string): void {
  undoStack.push(value);
  redoStack.length = 0;
  value = next;
}

function undo(): void {
  if (undoStack.length === 0) return;
  redoStack.push(value);
  value = undoStack.pop()!;
}

function redo(): void {
  if (redoStack.length === 0) return;
  undoStack.push(value);
  value = redoStack.pop()!;
}

Monotonic stack

Keep values increasing or decreasing so the top is always the next useful candidate.

next greater element

Pop while the top is smaller than the current value, then push.

monotonic-next-greater.tsTypeScript
// ——— next greater to the right ———
function nextGreater(nums: number[]): number[] {
  const n = nums.length;
  const out = new Array<number>(n).fill(-1);
  const stack: number[] = []; // indexes, increasing values

  for (let i = 0; i < n; i++) {
    while (stack.length > 0 && nums[stack.at(-1)!]! < nums[i]!) {
      const j = stack.pop()!;
      out[j] = nums[i]!;
    }
    stack.push(i);
  }

  return out;
}

nextGreater([2, 1, 2, 4, 3]); // [4, 2, 4, -1, -1]

when to use it

Reach for a monotonic stack when each index needs the next larger or smaller neighbor.

monotonic-sketch.tsTypeScript
// Pattern sketch (decreasing stack for “next smaller”):
// for each value x from left to right:
//   while stack is not empty and top >= x: pop
//   // top (if any) is the previous smaller
//   push x
//
// Same idea runs right-to-left for “previous” answers.