Algorithm Thinking

Making complex problems structurally simple.

Core Patterns โ€” Recognizing Structure Before Code

Strong engineers do not memorize solutions. They recognize structural moves.

A pattern is not a trick. It is a repeated structural strategy that appears across many problems.

Most patterns exist to eliminate repetition, preserve constraints, or change growth behavior.


Two Pointers

Two coordinated indices moving through data. Replace nested loops with synchronized movement.

function isPalindrome(str: string): boolean {
  let left = 0
  let right = str.length - 1

  while (left < right) {
    if (str[left] !== str[right]) return false
    left++
    right--
  }

  return true
}

Time: O(n)

Space: O(1)

Mental Shift

Coordinate movement instead of comparing everything.


Sliding Window

Maintain a window that always satisfies a constraint. Avoid recomputing overlapping work.

function maxSubarraySum(arr: number[], k: number): number {
  let sum = 0

  for (let i = 0; i < k; i++) {
    sum += arr[i]
  }

  let max = sum

  for (let i = k; i < arr.length; i++) {
    sum += arr[i] - arr[i - k]
    max = Math.max(max, sum)
  }

  return max
}

Time: O(n)

Space: O(1)

Mental Shift

Update incrementally while preserving a condition.


Prefix Sum

Precompute cumulative results to answer many range queries efficiently.

function buildPrefix(arr: number[]): number[] {
  const prefix = [0]

  for (let i = 0; i < arr.length; i++) {
    prefix.push(prefix[i] + arr[i])
  }

  return prefix
}

Build: O(n)

Query: O(1)

Mental Shift

Pay once. Query cheaply many times.


Binary Search

Repeatedly divide the search space. Only works when structure exists.

function binarySearch(arr: number[], target: number): number {
  let left = 0
  let right = arr.length - 1

  while (left <= right) {
    const mid = Math.floor((left + right) / 2)

    if (arr[mid] === target) return mid
    if (arr[mid] < target) left = mid + 1
    else right = mid - 1
  }

  return -1
}

Time: O(log n)


BFS (Breadth-First Search)

Explore level by level. Guarantees shortest path in unweighted graphs.

function bfs(root: Node) {
  const queue = [root]

  while (queue.length) {
    const node = queue.shift()!
    console.log(node.value)

    for (const child of node.children) {
      queue.push(child)
    }
  }
}

Time: O(V + E)


DFS (Depth-First Search)

Explore deeply before backtracking. Often pairs naturally with recursion.

function dfs(node: Node) {
  if (!node) return

  console.log(node.value)

  for (const child of node.children) {
    dfs(child)
  }
}

Time: O(V + E)


๐Ÿง  The Hidden Engine โ€” Invariants

Every strong pattern preserves something. That preserved truth is the invariant.

If you cannot state the invariant clearly, you do not fully understand the pattern.

Invariants make algorithms predictable.


๐Ÿง  What All Patterns Share


Final Insight

Patterns are not tricks.

They are structural moves guided by invariants.