Articles

Memoization vs. Tabulation in Dynamic Programming

Memoization caches results top-down via recursion; tabulation builds a table bottom-up with loops. Same technique, opposite direction, different tradeoffs.

The Lycoris Team The Lycoris Team · · 4 min read
Chalkboard covered in mathematical equations

Memoization and tabulation are the two implementation strategies for dynamic programming: both avoid recomputing the same subproblem twice by storing results, but they build those results in opposite directions. Memoization is top-down — it starts from the original problem and recurses, caching each subproblem’s answer the first time it’s computed. Tabulation is bottom-up — it starts from the smallest subproblems and iteratively fills a table until it reaches the answer to the original problem.

Memoization: recursion with a cache

Memoization takes a plain recursive solution and adds a lookup: before computing a subproblem, check whether it’s already been solved; if so, return the cached value instead of recomputing. The classic illustration is Fibonacci, where naive recursion recomputes the same values exponentially many times:

def fib(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
    return memo[n]

The shape of the code barely changes from the naive recursive version — you’re just adding a cache check at the top and a cache write before returning. That’s memoization’s main appeal: it lets you start from a correct-but-slow recursive solution and turn it efficient with a small, mechanical change, rather than redesigning the algorithm’s structure. The order subproblems get computed in follows naturally from the recursion — you never have to reason explicitly about “what needs to be solved before what.”

Tabulation: filling a table iteratively

Tabulation instead builds a table — usually an array — from the smallest subproblems upward, using a loop instead of recursion:

def fib(n):
    if n <= 1:
        return n
    table = [0] * (n + 1)
    table[1] = 1
    for i in range(2, n + 1):
        table[i] = table[i - 1] + table[i - 2]
    return table[n]

Here you have to explicitly decide the iteration order — computing table[i] requires that table[i-1] and table[i-2] already exist, so the loop has to visit indices in increasing order. For simple linear dependencies like Fibonacci this is easy, but for problems with more complex subproblem dependencies (like certain grid or interval DP problems), figuring out a valid iteration order that respects every dependency is real design work that memoization sidesteps by letting the recursion figure it out implicitly.

Why tabulation is usually faster in practice

Tabulation typically outperforms memoization for two concrete reasons. First, it avoids function call overhead — a loop iteration is cheaper than a recursive call, and in languages without tail-call optimization, deep recursion in memoization can also risk a stack overflow on large inputs, since recursion consumes stack frames that iteration doesn’t. Second, tabulation computes every subproblem in the table’s range, in a cache-friendly, sequential access pattern, whereas memoization’s recursive access pattern can jump around less predictably depending on the recursion structure.

Where memoization wins

Tabulation’s exhaustiveness is also its weakness for some problems: it computes every entry in the table, even subproblems the actual answer never depends on. Memoization only computes subproblems that are actually reached by the recursion, which matters when the full table would be large but the recursion only touches a sparse subset of it. A search over a huge state space where most states are unreachable from the starting configuration is a good example — tabulating the whole space would waste enormous amounts of work computing states nothing ever calls.

Memoization also tends to be easier to write correctly for problems with irregular or non-obvious subproblem dependencies, precisely because you don’t have to work out a valid iteration order by hand — the recursive call graph handles that implicitly.

Side-by-side comparison

Memoization (top-down)Tabulation (bottom-up)
DirectionOriginal problem → smaller subproblemsSmallest subproblems → original problem
ImplementationRecursion plus a cacheIteration filling a table
Computes unused subproblems?No — only what’s reachedYes — the whole table, by default
Stack overflow riskYes, on deep recursionNo
Typical overheadFunction call overhead per subproblemLower — loop iteration only
Easier to derive fromA working recursive solutionAn explicit dependency order

Choosing between them

Neither approach changes the underlying time complexity — both compute each distinct subproblem exactly once, so their asymptotic cost is identical. The choice comes down to engineering tradeoffs: reach for memoization when a recursive formulation is the natural way to express the problem, when the reachable subproblem space is meaningfully smaller than the full table, or when you’re prototyping and want to get a correct recursive solution working before optimizing it. Reach for tabulation when you need the best constant-factor performance, when the input size risks a stack overflow under recursion, or when the iteration order is simple enough that writing the loop is no harder than writing the recursion would have been.

The takeaway

Memoization and tabulation solve the same problem — don’t recompute a subproblem you’ve already solved — from opposite directions. Memoization layers a cache onto natural recursion and only computes what’s actually needed; tabulation iteratively fills a table bottom-up and usually runs faster with lower overhead, at the cost of having to reason about iteration order up front. Start with whichever direction makes the recursive structure of the problem clearest, and switch to tabulation later if the recursion depth or call overhead becomes a real bottleneck.

The Lycoris Team The Lycoris Team · · 4 min read

Manacher's Algorithm Explained

Manacher's algorithm finds the longest palindromic substring in linear time by reusing symmetry from palindromes already found, avoiding redundant checks.

#Computer Science #Algorithms #Data Structures
The Lycoris Team The Lycoris Team · · 4 min read

The Z-Algorithm for String Matching, Explained

The Z-algorithm builds a Z-array in linear time to find every occurrence of a pattern in a text — a fast alternative to naive string search.

#Computer Science #Algorithms #Data Structures
The Lycoris Team The Lycoris Team · · 5 min read

Binary Search Algorithm Explained

Binary search finds a value in a sorted array in O(log n) time by repeatedly halving the search space. How it works, why it needs sorted input, and common pitfalls.

#Computer Science #Algorithms #Data Structures