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.
- What operations must be fast?
- Am I scanning repeatedly?
- Does order matter?
- Is this hierarchical?
- Are relationships arbitrary?
- Do I need priority ordering?
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
- Order matters
- Iteration is primary
- Lookup speed is not critical
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
- You need fast lookup
- You are eliminating repeated scans
- You are counting or caching
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
- Duplicate detection
- Fast existence checks
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
- Undo behavior
- Balanced expressions
- Simulating recursion
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
- Level traversal
- Shortest path (unweighted)
- Ordered task processing
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
- Modeling hierarchy
- Nested structures (DOM, file systems)
- Recursive decomposition
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
- Modeling relationships
- Networks and dependencies
- Pathfinding problems
🧠 Mental Model Summary
- Structures define what is cheap.
- Arrays optimize iteration.
- Maps optimize lookup.
- Sets optimize membership.
- Stacks manage reversal and backtracking.
- Queues manage level processing.
- Trees model hierarchy.
- Graphs model arbitrary relationships.
Final Insight
Optimization rarely begins with code.
It begins with modeling access and state correctly.
When structure is correct, many complexity problems disappear automatically.