Memoization is an optimization technique that ensures a function doesn't run for the same inputs more than once by keeping a record of the results for the given inputs (usually in a dictionary). When the function gets called again with inputs it's already seen, it returns the saved result instead of recomputing it.
For example, a simple recursive function for computing the nth Fibonacci number:
Will run on the same inputs multiple times:
We can imagine the recursive calls of this function as a tree, where the two children of a node are the two recursive calls it makes. We can see that the tree quickly branches out of control:
To avoid the duplicate work caused by the branching, we can wrap the function in a class with an attribute, memo, that maps inputs to outputs. Then we simply
- check memo to see if we can avoid computing the answer for any given input, and
- save the results of any calculations to memo.
We save a bunch of calls by checking the memo:
Now in our recurrence tree, no node appears more than twice:
That takes us from exponential time down to linear time:
| Computing the nth Fibonacci number | Time | Space |
|---|---|---|
| Plain recursion | O(2^n) | O(n) for the call stack |
| Recursion with memoization | O(n) | O(n) for the memo and the call stack |
| Bottom-up, keeping just the last two numbers | O(n) | O(1) |
Memoization is a common strategy for dynamic programming problems, which are problems where the solution is composed of solutions to the same problem with smaller inputs (as with the Fibonacci problem, above). The other common strategy for dynamic programming problems is going bottom-up, which is usually cleaner and often more efficient.
Memoization vs. Tabulation (Bottom-Up)
Going bottom-up is also called tabulation, since it usually fills in a table of answers to subproblems, starting from the smallest. Memoization is the top-down approach. Both are ways to do dynamic programming—they just tackle the subproblems in opposite directions:
| Memoization (top-down) | Tabulation (bottom-up) | |
|---|---|---|
| Direction | Starts from the full problem and recurses down to the base cases | Starts from the base cases and builds up to the full problem |
| How it's written | Recursion plus a cache | A loop that fills in a table (often a list) |
| Which subproblems get solved | Only the ones the recursion actually reaches | Usually all of them, in order |
| Extra space | The memo, plus a call stack that can get deep enough to overflow on big inputs | Just the table, and you can often shrink it (like keeping only the last two Fibonacci numbers) |
| Easier when | The recursive solution is natural and you just want to stop repeating work | You can see the order the subproblems need to be solved in |
In an interview, a good approach is to write the recursive solution, add memoization to speed it up, and then (if there's time) convert it to a bottom-up solution to save space.
Memoization/Dynamic Programming interview questions to practice
Each one walks you through a real coding interview problem one hint at a time.
- Compute the nth Fibonacci Number » Computer the nth Fibonacci number. Careful--the recursion can quickly spin out of control!
- Making Change » Count the number of ways to make an amount of money from a set of coin denominations (the number-of-ways version of coin change). Step-by-step solution.
Your first 3 questions are free. Just sign in, no credit card. More Memoization/Dynamic Programming questions
See also:
Interview coming up?
Get the free 7-day email crash course. You'll learn how to think algorithmically, so you can break down tricky coding interview questions.
No prior computer science training necessary—we'll get you up to speed quickly, skipping all the overly academic stuff.
No spam. One-click unsubscribe whenever.
You're in! Head over to your email inbox right now to read day one!
Interview Cake