Data Structures

Understanding Stack Data Structure in TypeScript: Implementation and Use Cases

A stack is last-in, first-out. Think of a stack of plates. You add to the top. You take from the top. You never pull from the middle.

20 Apr 2024

Understanding Stack Data Structure in TypeScript: Implementation and Use Cases

A stack is last-in, first-out. Think of a stack of plates. You add to the top. You take from the top. You never pull from the middle.

That constraint seems limiting, but it's exactly what makes stacks powerful. Every function call your program makes uses a stack. Every undo/redo system is a stack. Every expression parser uses a stack.

Implementation

Typescript
class Stack<T> {
  private items: T[];

  constructor() {
    this.items = [];
  }

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

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

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

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

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

All operations are O(1). Push adds to the top. Pop removes from the top. Peek looks without removing.

The Call Stack

Every time your program calls a function, the runtime pushes a frame onto the call stack. When the function returns, the frame pops off. This is why deeply recursive functions can cause a "stack overflow" -- the stack runs out of space.

Typescript
function factorial(n: number): number {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}
// Each recursive call adds a frame to the call stack
// factorial(5) → factorial(4) → factorial(3) → factorial(2) → factorial(1)
// Then they unwind in reverse order

Where Stacks Show Up

  • Undo/Redo: Every action pushes onto the undo stack. Undo pops it off and pushes it onto the redo stack.
  • Browser history: The back button pops from the history stack. Forward pushes it back.
  • Expression evaluation: Parsing mathematical expressions, validating balanced brackets, converting infix to postfix -- all stack problems.
  • DFS traversal: Depth-first search uses a stack (explicitly or via recursion) to explore as deep as possible before backtracking.
  • Syntax parsing: Compilers use stacks to parse nested structures like function calls, blocks, and parentheses.

Practical Example: Balanced Brackets

A classic stack problem. Check if every opening bracket has a matching closing bracket:

Typescript
function isBalanced(input: string): boolean {
  const stack = new Stack<string>();
  const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' };

  for (const char of input) {
    if ('([{'.includes(char)) {
      stack.push(char);
    } else if (')]}'.includes(char)) {
      if (stack.isEmpty() || stack.pop() !== pairs[char]) {
        return false;
      }
    }
  }
  return stack.isEmpty();
}

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

The stack is deceptively simple. Two operations, one constraint, and it solves an entire category of problems. If your problem involves nested structures, reverse-order processing, or backtracking, a stack is probably the answer.

Keep reading