Skip to content
ByteStepStart with recursion

Dynamic programming

Free, no account

Dynamic programming is one observation with an unhelpful name. The observation: if the same smaller problem keeps coming back, work it out once and write the answer down. The name was chosen in the 1950s because it sounded impressive to the people holding the budget, and it has been confusing students ever since.

Press play. The tree below is fib(6) worked out the direct way, and it takes 25 calls. Watch how often the same small number appears: one of them is worked out 8 separate times, on different branches, each time from scratch.

Then press fib-memo, which is the same function with three lines added. Same number, same answer, 11 calls. Nothing was made faster. Things simply stopped happening twice.

1function fib(k) {
2 if (k <= 1) return k
3
4 return fib(k - 1) + fib(k - 2)
5}

The call tree

every call splits again, however many times it has been made before

The subproblems

one slot each, and how much work it has cost

Times worked out

  1. ·
  2. ·
  3. ·
  4. ·
  5. ·
  6. ·
  7. ·

0 calls so far

Working out fib(6) the direct way, remembering nothing between calls.

1/39
Detail
39 steps

What the repeats in the tree mean, what the table takes away, and when it takes away nothing.

What the tree is really saying

Every node is labelled with the subproblem it is solving, and that is the only label it needs, because two nodes carrying the same number are the same work being done twice. Run the plain version and the tree is full of repeats: the lower you look, the more of them there are.

That is overlapping subproblems, and it is the condition the whole technique depends on. The branches under any call are not separate problems, they are two views of almost the same problem, and a plain recursion has no way to notice.

The collapse

Now watch fib-memo. When a call is asked for a second time, it does not split. It appears faded, already holding its answer, and the entire subtree that would have grown beneath it never happens.

That is the thing worth understanding about memoisation: the saving is never one call. It is that call, and everything it would have gone on to ask for, and everything under that. At 6 the table is read 4 times and the tree goes from 25 nodes to 11. At 14 the plain version would need 1,219 calls and the remembering one needs 27.

The row on the right counts how many times each subproblem is actually worked out. Without the table it is a spiky mess. With it, every bar is a one. That row is the entire technique in one picture.

When remembering buys nothing

Press factorial-memo. The table is there, the code to use it is there, and it is read 0 times.

Each call asks for something nothing has asked for before, so nothing is ever read back. The calls form a chain rather than a branching tree, and a chain has no overlap to exploit. The memory is not free: it costs space, and here it buys nothing whatsoever.

This is the half that usually goes missing. Recursion plus a table is not dynamic programming. Recursion plus a table plus something actually coming back is.

Know someone stuck on this? Send it to them.

A classmate, a study group, someone learning this on their own at midnight. The link opens the lesson set up exactly as you have it, the same function and the same number, and runs from the first step so they watch the whole thing build rather than landing in the middle of it.

Also in this course

Greedy algorithms, and when they are wrong

The lesson that ends with six coins where three would do. The fix is a table of every amount, each answer built from one already worked out.

Start there →