Introduction
There are roughly six families of algorithm that every software engineer eventually meets — in a university course, a coding interview, or the moment a real production problem refuses to scale. Memorising line-for-line implementations is the wrong goal. Understanding how each family thinks, when it breaks, and what its big-O actually means is the goal that pays off.
This is a practical walkthrough of those six families: recursion, search, sorting, pathfinding, minimum spanning trees, and dynamic programming. Each section gives the mental model first, then the concrete examples (Fibonacci, binary search, merge sort, Dijkstra’s, Kruskal’s, minimum-sum subarray), and finally the time/space trade-offs that decide which one you reach for in production.
📚 Table of contents
- Recursion — base cases, recursive cases, and Fibonacci three ways
- Memoization and the iterative escape hatch
- Binary tree traversals — in-order, pre-order, post-order
- Searching — linear vs binary
- DFS and BFS on a maze
- Sorting algorithms — the inefficient three and the efficient three
- Pathfinding — Dijkstra’s and A*
- Minimum spanning trees — Kruskal’s algorithm
- Dynamic programming — the minimum-sum subarray example
- Common mistakes & pro tips
- Frequently asked questions
🔁 Recursion
Recursion means a function that calls itself. Every recursive algorithm needs two ingredients: at least one base case (where the answer is already known) and at least one recursive case (where the function calls itself with a smaller problem). The trick is divide and conquer — break a hard problem into smaller versions of itself, solve those, combine.
Fibonacci, three ways
Naive recursive
fib(n) = fib(n-1) + fib(n-2).
Beautiful, useless past n = 40. Time complexity is exponential
(O(2^n)) because every level
re-computes the levels below.
Memoized recursive
Add a cache keyed by n. Each value
is computed exactly once. Drops to O(n)
time, O(n) space.
fib(70) returns instantly.
Iterative
Walk forward with two variables — last and second last. Same
O(n) time,
O(1) space. No call stack, no cache.
The Fibonacci progression is the canonical lesson: recursion is a thinking tool, memoization is the standard optimisation, and iteration usually wins on space. Most production code lives in the iterative column.
Binary tree traversals
Every binary-tree traversal is three lines of recursion plus a base case. node === null
is the base case. The difference is when you visit the current node:
- In-order: left → node → right. Sorted output on a BST.
- Pre-order: node → left → right. Useful for copying or serialising a tree.
- Post-order: left → right → node. Useful for deleting a tree bottom-up.
Recursion as a creative tool
Even string reversal — trivially a for-loop — can be expressed recursively as reverse(rest of string) + first character. The structure shows up in parsing, traversal, backtracking, divide-and-conquer sorts, and graph search. Once the base case + recursive case shape is in your head, reading a new recursive algorithm becomes a translation exercise instead of a puzzle.
🔎 Searching: linear and binary
The simplest question: is x in this array?
Linear search
Walk every element until you find a match or exhaust the array. Works on any list.
O(n) time, no preconditions.
Binary search
Requires a sorted array. Repeatedly look at the middle element, eliminate half the array, repeat.
O(log n) time. A thousand elements
becomes ~10 comparisons, a million becomes ~20.
A clean recursive implementation tracks start
and end indexes instead of slicing the
array:
- Base case:
start > end→ not found. - Compute
middle = floor((start + end) / 2). - If
arr[middle] === target→ found. - If
arr[middle] > target→ recurse on(start, middle - 1). - Otherwise → recurse on
(middle + 1, end).
👉
If you need to search a list repeatedly, sorting once
(O(n log n)) and then doing many binary
searches usually beats repeated linear scans.
🧭 DFS and BFS on a maze
Both depth-first search (DFS) and breadth-first search (BFS) explore every reachable cell — they just disagree about order. Use them to traverse trees, graphs, or grid mazes.
DFS — the snake
Backed by a stack. Goes as deep as possible down one path before backtracking. Push neighbours, pop the most-recent one, repeat. Tends to find a path quickly, not necessarily the shortest.
BFS — the expanding circle
Backed by a queue. Explores all neighbours at distance 1 before any at distance 2. Gives you the shortest path in an unweighted graph for free.
Implementation skeleton
- Seed the stack/queue with the start position.
- Keep a visited set to avoid cycles — serialise grid positions to strings (“y,x”) since arrays don’t deduplicate in JS sets.
- Pop / shift; if it’s the goal, stop.
- Otherwise compute valid neighbours (in bounds, not a wall, not visited) and push them.
Time complexity is O(W × H) for a
grid — you can’t avoid visiting cells you need to visit. Watch out for BFS when the
“queue” is just an array: array.shift()
is O(n) in JavaScript and silently
blows your complexity up to O((W × H)^2).
Use a linked list or an index-based queue for real workloads.
🗂️ Sorting algorithms
Two properties matter when you compare sorts. Stability: elements with equal keys keep their original relative order. In-place: no extra array — sort by mutating the original. Now the algorithms split into two camps.
Inefficient — O(n²)
- Selection sort — repeatedly pick the smallest from the unsorted portion and swap to the front.
- Insertion sort — insert each new element into its correct place among the already-sorted prefix.
- Bubble sort — swap adjacent pairs that are out of order; repeat until no swaps happen.
Efficient — O(n log n)
- Merge sort — recursively split, then merge sorted halves. Stable, not in-place by default.
- Heap sort — build a max-heap, repeatedly extract the root. Unstable.
- Quick sort — partition around a pivot, recurse on each side. Average O(n log n), worst case O(n²) on a bad pivot.
💡
Most languages ship one of the efficient sorts as the default. JavaScript’s
Array.prototype.sort is typically
Timsort under the hood — a hybrid of merge sort and insertion sort. Assume
O(n log n) when reasoning about
built-in sorts.
Merge sort, mechanically
Split the array in half recursively until each subarray has one element (sorted by definition). Then merge sibling subarrays back together by walking both and picking the smaller head into a new array. Each merge is linear; the recursion is log n deep; total O(n log n). Space is O(n) for the merge buffers.
🗺️ Pathfinding: Dijkstra’s and A*
BFS gives you shortest paths on unweighted graphs. The moment edges have weights — cost, distance, time — you need something smarter.
Dijkstra’s algorithm
- Initialise every node’s distance to infinity, except the start (distance 0).
- Push the start into a priority queue (min-heap).
- Pop the node with the smallest known distance. For each neighbour, relax its distance if going through the current node beats the existing value.
- Repeat until the queue empties or the target pops.
Runs in O((V + E) log V) with a binary
heap. Requires non-negative weights — negative edges break the invariant.
A* — Dijkstra plus a heuristic
A* is the version most people actually ship for grid pathfinding. It adds a heuristic — a cheap-to-compute estimate of the remaining distance to the goal (Euclidean or Manhattan on a grid). The priority queue is keyed by current distance + heuristic, so the search biases toward the goal instead of expanding in every direction equally.
The win is real: in an open grid, A* will often touch a tiny fraction of the nodes Dijkstra would. In maze-heavy environments with obstacles, A* still has to backtrack, but the heuristic keeps it from wasting time on obviously-wrong directions.
🌲 Minimum spanning trees
A spanning tree of a connected graph is a set of edges that touches every vertex without forming a cycle. A minimum spanning tree is the spanning tree whose total edge weight is smallest. Useful for laying out networks, pipelines, circuits — anywhere you want full connectivity at minimum cost.
Kruskal’s algorithm
- Sort all edges by weight, ascending.
- Initialise each vertex in its own component (Union-Find / Disjoint Set).
- Walk the sorted edge list. For each edge, if it connects two different components, add it to the MST and union the components.
- Stop once the MST has
V - 1edges.
Time complexity is dominated by the edge sort: O(E log E).
Union-Find with path compression and union by rank brings the union/find operations to effectively
constant time per call.
👉 Prim’s algorithm is the alternative — it grows the MST one node at a time from a starting vertex, similar in spirit to Dijkstra. Kruskal tends to be easier to reason about when you already have the edge list; Prim is often faster on dense graphs.
🧱 Dynamic programming
Dynamic programming is what you reach for when a problem has overlapping subproblems — the same sub-questions keep coming up — and optimal substructure — the answer to the big problem is built from answers to small ones. Memoized recursion is one DP style; bottom-up tabulation is the other.
Worked example: minimum-sum contiguous subarray
Given an array, find the contiguous subarray with the smallest possible sum. The DP insight:
At each index i, the minimum sum of a
subarray ending at i is either
arr[i] alone, or
arr[i] + (min-sum ending at i-1). Take
the smaller.
Track the running “min sum ending here” and the global minimum as you scan. One pass,
O(n) time. If you store every
intermediate value, space is O(n) —
but you only ever need the previous value, so the optimised version uses
O(1) space. That space optimisation is
the same trick that turned recursive Fibonacci into a two-variable loop.
When to suspect DP
- You wrote a naive recursive solution that times out, and the call tree visibly repeats work.
- The problem statement says “maximum”, “minimum”, “count the ways”, or asks for an optimal arrangement.
- Each decision depends only on a small, well-defined slice of earlier state — the previous element, the previous row, the last k items.
🚫 Common mistakes & pro tips
Mistakes
- Memorising implementations instead of mental models.
- Using binary search on an unsorted array (or one that’s sorted by a different key than you think).
- Inserting arrays into a JS
Setexpecting deduplication — serialise to a string first. - Building a queue with
Array.shift()and tanking BFS performance. - Picking quick sort for adversarial inputs — the worst case is real.
- Reaching for DP before checking whether a greedy solution exists.
Pro tips
- For recursion, write the base case first. Always.
- If the call tree shows repeated subproblems, add memoization before optimising further.
- Sort once for repeated searches; let binary search pay back the sort cost.
- Use BFS when you need shortest paths in an unweighted graph; DFS when any path will do.
- For pathfinding, A* with a Manhattan heuristic is the default on grids.
- For DP, write the recurrence in words first, then in code.
🎯 Conclusion
These six families — recursion, search, sorting, pathfinding, MST, and DP — show up over and over in interviews and in production. The honest goal isn’t to memorise every implementation, it’s to recognise the shape of each problem fast enough that you can pick the right family in minutes and adapt the canonical algorithm to the specifics in front of you.
Build the mental models, internalise the time and space complexities, and the implementation almost falls out of the description. The next time a coworker says “this is too slow,” you’ll catch yourself thinking which family of algorithm is this, and how do its trade-offs change the answer? — which is exactly the muscle worth building.
Explore More on DevShelf
-
AI Engineer Job Market 2026
Data on which roles are growing — and which still require the algorithm fundamentals covered here for technical interviews.
-
Learn Agentic AI in 7 Steps
Next step after algorithms — the structured path from LLM fundamentals to production AI agents.