Algorithm Thinking

Making complex problems structurally simple.

Data Structures — Modeling Before Optimizing

Most algorithm problems are not solved by clever code. They are solved by choosing the correct structure.

Data structures define access patterns.

When you choose the right structure, complexity often disappears before optimization begins.


🧠 The Core Question

Structure answers these questions before code does.

The structure you choose defines what becomes cheap and what becomes expensive.


Array

A sequential container with indexed access. Optimized for iteration.

const arr = [10, 20, 30]
const first = arr[0]

Access: O(1)

Search: O(n)

function findUser(users, name) {
  for (const user of users) {
    if (user.name === name) return user
  }
  return null
}

Time: O(n)

Reach For This When


Hash Map (Map / Object)

Key-based lookup in constant average time. Optimized for direct access.

const usersById = new Map()
usersById.set("u1", { name: "Camilo" })
const user = usersById.get("u1")

Average Lookup: O(1)

function countWords(words) {
  const counts = new Map()

  for (const word of words) {
    counts.set(word, (counts.get(word) || 0) + 1)
  }

  return counts
}

Time: O(n)

Reach For This When


Set

Uniqueness with constant-time membership checks. Optimized for existence queries.

function hasDuplicates(arr) {
  const seen = new Set()

  for (const value of arr) {
    if (seen.has(value)) return true
    seen.add(value)
  }

  return false
}

Time: O(n)

Reach For This When


Stack (LIFO)

Last in, first out. Natural model for backtracking and structural reversal.

function isValidParentheses(str) {
  const stack = []

  for (const char of str) {
    if (char === "(") stack.push(char)
    else {
      if (stack.length === 0) return false
      stack.pop()
    }
  }

  return stack.length === 0
}

Push/Pop: O(1)

Reach For This When


Queue (FIFO)

First in, first out. Processes work in arrival order.

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

  while (queue.length > 0) {
    const node = queue.shift()

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

Enqueue/Dequeue: O(1)

Reach For This When


Tree

Trees introduce hierarchical state — where local decisions affect substructures.

Trees are recursive by definition — each subtree mirrors the whole.

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

  console.log(node.value)
  dfs(node.left)
  dfs(node.right)
}

Traversal: O(n)

Reach For This When


Graph

Graphs introduce non-linear relationships — structure is no longer constrained by hierarchy.

A graph generalizes trees. It allows cycles and arbitrary relationships.

const graph = {
  A: ["B", "C"],
  B: ["D"],
  C: [],
  D: []
}
function traverse(node, visited = new Set()) {
  if (visited.has(node)) return
  visited.add(node)

  for (const neighbor of graph[node]) {
    traverse(neighbor, visited)
  }
}

Traversal: O(V + E)

Reach For This When


🧠 Mental Model Summary


Final Insight

Optimization rarely begins with code.

It begins with modeling access and state correctly.

When structure is correct, many complexity problems disappear automatically.