learn
Weeks 31–34 · About 40 hours

Choosing the right approach

The previous stages gave you tools. This one is about recognising which tool an unfamiliar problem calls for.

Weeks 15 to 30 built a toolbox. This stage is about diagnosis — looking at a problem you have never seen and knowing, within a few minutes, what kind of problem it is.

This is the part that actually transfers to engineering work. Real problems do not arrive labelled. The skill of recognising “this is a graph problem wearing a disguise” is what separates knowing the from being able to use them.

Brute force

Trying every possibility with no cleverness at all. Usually too slow to ship, and almost always the right place to start.

Write the obvious, exhaustive version first. It is quick to write and almost certainly correct, and that makes it your reference: when you write the fast version, you can run both on random inputs and check they agree. Optimised code is where bugs hide, and a known-correct baseline is the cheapest testing you will ever do.

It also tells you whether optimising is needed at all. If the input is never more than fifty items, the brute-force version may simply be the right answer, and time spent making it clever is time wasted.

Where you meet it in real softwareThe first draft of anything, and the correctness check on every optimisation that follows.

Divide and conquer

Split the problem into smaller versions of itself, solve each, then combine the answers.

Merge sort is the canonical example: split in half, sort each half, merge. Quicksort is another. The shape recurs constantly once you look for it.

The Master Theorem is a formula for working out the Big-O of a divide-and-conquer algorithm from three facts: how many pieces you split into, how much smaller each piece is, and how much work the combining takes. It is worth being able to apply, and not worth memorising the proof of.

The same shape scales far beyond single programs. MapReduce, and distributed data processing generally, is across many machines rather than many function calls.

Where you meet it in real softwareMerge and quick sort, signal processing, distributed data processing, closest-pair geometry problems.

Greedy algorithms

Always take whatever looks best right now, and never reconsider. Fast, simple, and correct only for certain problems.

Giving change is the intuitive example: to make 87p, take the largest coin that fits and repeat. With normal currency this always gives the fewest coins. With a made-up currency it does not, and that is exactly the danger.

A greedy approach is correct only when the problem has two properties. The greedy choice property: taking the locally best option never rules out the overall best answer. And optimal substructure: the best answer contains the best answers to its sub-parts. If you cannot argue that both hold, do not trust a greedy solution — test it against on small random inputs.

When it does work it is beautiful. Huffman coding builds the compression tables inside ZIP, JPEG and MP3 by greedily merging the two least frequent symbols. Interval scheduling — fitting the most meetings into a room — is greedy on earliest finishing time.

Where you meet it in real softwareCompression, scheduling, cache eviction policies, minimum spanning trees.

Backtracking

Try a possibility; the moment it clearly cannot work, abandon it and step back to try something else.

Think of it likeSolving a maze. Walk down a corridor, and reverse out the instant you hit a dead end rather than continuing to stare at the wall.

It is exhaustive search with pruning. You still explore possibilities, but you cut off entire branches as soon as they are provably hopeless, which can remove astronomically many combinations without ever checking them.

Solving Sudoku is the standard example: place a digit, check whether the grid is still valid, and if not, undo it immediately rather than filling the remaining eighty squares first.

The structure is always the same: choose, explore, un-choose. That last step — undoing your change before trying the next option — is where the bugs live.

Where you meet it in real softwareSudoku and constraint solvers, N-Queens, regular expression engines, puzzle and game AI.

Dynamic programming

Noticing that you are solving the same small problem over and over, and remembering the answer instead of recomputing it.

Think of it likeWorking out a long sum and writing intermediate totals in the margin, rather than re-adding the same column every time you need it.

Go back to the Fibonacci you wrote in week 14. To compute Fibonacci of 40 it computes Fibonacci of 38 twice, Fibonacci of 37 three times, and so on — hundreds of millions of calls for an answer involving forty numbers. Add a dictionary that remembers each answer the first time and it returns instantly. That is the entire idea.

There are two ways to write it. is top-down: keep the natural recursion and cache the results. In Python, adding @functools.cache above a function does this for you in one line, which makes it a very cheap experiment. Tabulation is bottom-up: work out the smallest cases first and build up in a table, avoiding recursion entirely.

A problem is suitable when it has overlapping subproblems — the same smaller question comes up repeatedly — and optimal substructure, meaning the best overall answer is built from best answers to the smaller questions. Spotting those two properties is the actual skill.

Work the canonical ladder in order, because each rung adds exactly one idea: Fibonacci, climbing stairs, coin change, the 0/1 knapsack, longest common subsequence, longest increasing subsequence, edit distance, matrix chain multiplication. Skipping ahead does not work here.

Allocate more time to this than to anything else in the stage. It is the hardest topic in the roadmap for most people, and the one that pays off most in real work.

Where you meet it in real softwareThe diff algorithm behind git merges, spell-check and fuzzy search, DNA sequence alignment, resource allocation, financial optimisation.

Two pointers, sliding windows and prefix sums

Three tricks that turn a loop-inside-a-loop into a single pass.

keeps track of two positions at once. On sorted data, to find a pair adding to a target, start one at each end: if the sum is too big move the right one in, if too small move the left one out. One pass instead of checking every pair.

A looks at a stretch of the list and moves it along, adding the item entering and removing the item leaving, rather than re-examining the whole stretch each time. Any question about the best run of k consecutive items is this.

Prefix sums prepare a table of running totals once, after which any range total is a single subtraction. Preparing costs one pass; every query afterwards is instant.

All three are the same underlying insight: do not throw away the work you did on the previous step. That is worth internalising as a general habit, not as three separate tricks.

What it costsEach typically reduces an O(n²) approach to O(n).

Where you meet it in real softwareRate limiting over a time window, moving averages in monitoring dashboards, deduplicating sorted streams, substring search.

Bit manipulation

Working directly with the individual 0s and 1s a number is made of.

A useful first one: exclusive-or has the property that a number combined with itself cancels to zero. So combining every item in a list where everything appears twice except one leaves exactly that one, using no extra memory at all.

Bitmasks let a single number carry many yes/no flags at once — one bit per permission, per feature toggle, per option. Compact and fast to check.

The trick n & (n-1) clears the lowest set bit, which gives a neat way to count how many 1s a number contains. Python has int.bit_count() built in, but knowing why the trick works is the point.

This is the least broadly useful topic in the stage. Learn the common patterns and move on; do not sink days into it.

Where you meet it in real softwarePermission systems and feature flags, Bloom filters, enumerating subsets inside , low-memory presence tracking.

Reading about this stage is not the same as finishing it. Type every one of these from a blank file rather than copying — the writing is the part that teaches.

The dynamic programming ladder

Work through Fibonacci, climbing stairs, coin change, 0/1 knapsack, longest common subsequence, longest increasing subsequence and edit distance — in that order. Write each one both memoised and tabulated. This is the single biggest time investment of the stage and it is correctly placed.

Write a tiny diff tool

Use longest common subsequence to compare two text files and print which lines were added and removed. You will have written a simplified version of what git shows you every day.

Break a greedy algorithm on purpose

Write greedy coin change, then invent a currency where it gives the wrong answer, and confirm it by comparing against a brute-force solution. Experiencing greedy failing is what stops you trusting it blindly.

Sudoku solver

Backtracking, end to end. Choose, explore, un-choose. Then add better pruning — always filling the most constrained square next — and measure how much faster it gets.

Exit checkGiven a problem you have never seen, you can name the likely approach within five minutes and justify why. Around 300 problems in total — though see the note below on volume.

If you cannot do this, repeat the stage rather than moving on. Nothing later gets easier by skipping it.

Worth knowing before you start

  • Dynamic programming is where people quit. It is normal for it to take several attempts before it clicks. Work the ladder in order rather than jumping to hard problems.
  • The 300-problem figure is calibrated for interview preparation. For general engineering skill, around 150 well-understood problems beat 300 rushed ones. Depth over volume.

This stage corresponds to DSA Phase 5 — Problem-Solving Paradigms in the source roadmap.