Fifteen Problem Shapes and the Move That Solves Each
The usual way to learn algorithms is to work through a long list of problems and hope something generalises, which leaves most people with a hundred solutions memorised and no idea what to do with the hundred and first. There are perhaps a dozen shapes of problem, each with a move that usually works. Fifteen of those shapes and how to recognise them.
Tauseef Fayyaz

Pattern recognition, not memorisation
The usual way to learn algorithms is to work through a long list of problems and hope something generalises. Sometimes it does. More often people finish with a hundred solutions memorised, meet the hundred and first, and discover they learned answers rather than the thing that produces answers.
What actually transfers is smaller. There are perhaps a dozen shapes of problem, each with a move that usually works, and most of the difficulty is recognising which shape you are holding. An experienced engineer looking at a new problem is not recalling a solution. They are noticing that the input is sorted, or that the same subproblem keeps reappearing, and reaching for the corresponding move.
Below are fifteen of those pairings. The order is roughly by how often they come up in real code rather than by how impressive they sound.
Removing a loop you did not need
1. Repeated lookups + a list → a hash map. The most valuable algorithmic move there is, and the least glamorous. A scan inside a loop is quadratic; a lookup inside a loop is linear. A large share of accidentally slow code is exactly this and nothing more.
2. Repeated questions about order + one dataset → sort once, up front. Sorting costs you a single logarithmic factor and then makes duplicates adjacent, extremes reachable and binary search possible. If you are going to ask many questions of the same data, pay once.
3. Sorted data + a search → binary search. Halving the space at each step is the difference between a million comparisons and twenty. It is also the algorithm most people get subtly wrong from memory, so write it carefully once and remember that the tricky part is always the boundary condition.
4. Sorted data + finding a pair → two pointers. One from each end, moving inward based on whether the current sum is too big or too small. It turns an obvious quadratic search into a single pass, and once you have seen it, a whole family of problems collapses into the same three lines.
Working across a sequence
5. A contiguous run + a constraint → a sliding window. Longest substring with some property, maximum sum of a fixed length. Grow the window on the right, shrink it from the left when the constraint breaks, and never look at the same element more than twice.
6. Many range sums + data that does not change → prefix sums. Precompute the running total once and any range becomes one subtraction. This is the cheapest possible example of the general principle that repeated queries justify preprocessing.
7. The top k items + a large stream → a heap of size k. Do not sort a million things to find the ten biggest. Keep k of them and evict the weakest, which turns the memory requirement from the size of the data into the size of the answer.
When the answer is a search through possibilities
8. A big problem + two smaller identical ones → divide and conquer. Split, solve each half, combine. Merge sort and quicksort are the famous cases, but the pattern matters more than either, because recognising it is what turns a linear scan into a logarithmic one.
9. Every combination + constraints that rule most of them out → backtracking with pruning. Build the answer one choice at a time, and abandon a branch the moment it cannot possibly work. The pruning is not an optimisation detail. It is usually the entire difference between feasible and not.
10. Overlapping subproblems + a best answer → dynamic programming. If your recursion computes the same thing repeatedly, cache it. Start by writing the honest recursive solution, then add memoisation, and only convert it to a table if you need to. Almost nobody arrives at the table directly, whatever the textbooks imply.
11. A locally obvious choice + a reason it stays correct → greedy. Greedy algorithms are short, fast, and wrong more often than people expect. The heuristic is not "take the best local option", it is "take the best local option and then convince yourself with an actual argument that it cannot paint you into a corner".
Once it is a graph
12. The fewest steps + edges that all cost the same → breadth first search. Layer by layer with a queue. The first time you reach the target is the shortest path, and that guarantee disappears the moment the edges have different weights.
13. Reachability, cycles or ordering → depth first search. Can I get there, does this depend on itself, what order can these run in. Topological sorting falls out of this directly and is what every build system and task scheduler is doing underneath.
14. Shortest path + edges with weights → Dijkstra. Breadth first search with a priority queue instead of a plain one, which is a genuinely useful way to remember it. Know also that it breaks on negative weights, because knowing where an algorithm stops being correct is more useful than knowing how it works.
15. A clever algorithm + no measurement → use the simple one. A quadratic loop over twelve items is fine forever. The sophisticated version costs reader comprehension every time someone opens the file, and that cost is real and recurring while the speed gain may be entirely imaginary.
What else belongs on this list
Hashing itself, one level down, because knowing why a hash map degrades helps you understand when it will. Randomisation, which turns a worst case into an unlikely case and is how quicksort survives. Bit manipulation, which is narrow but unreasonably effective in the few places it applies.
And the meta heuristic that outranks all of them: get a correct brute force solution working first, always. It gives you a reference to test the fast version against, it often reveals the structure you needed to see, and often enough it turns out to be fast enough that you never write the second one.
The short version
Hash map to remove a nested loop. Sort once when you will ask many questions. Binary search and two pointers on sorted data. Sliding window for contiguous runs, prefix sums for repeated ranges, a small heap for top k. Divide and conquer, backtracking with pruning, memoise overlapping subproblems, and prove your greedy choice before trusting it. Breadth first for unweighted shortest paths, depth first for structure, Dijkstra when the edges have weights.
Fifteen shapes, and recognising which one you are looking at is the skill. The implementations are the easy half, and they are also the half you can look up.
Comments (0)
Comments are closed for now.
No comments yet.
Stuck on something specific?
Writing only gets you so far. If you want an answer to your situation rather than the general case, book a session and we will work through it together. Every session is free; a few slots open each week.
Follow along
New writing, resources and project ideas land here first.