Skip to main content

LeetCode Patterns

Memoization (Top-Down DP) Pattern

The Memoization pattern wraps a natural recursive solution with a cache keyed on the function's arguments, so each unique subproblem is solved exactly once. It is the top-down face of dynamic programming — write the recurrence first, then add the cache. Time drops to O(states * work-per-state); space is dominated by the cache plus the recursion stack.

By Alex Chen, Founder, InterviewChamp.AI · Last verified

Complexity

Time
O(n)
Space
O(n)

When to use Memoization (Top-Down DP)

  • You can articulate a clean recursive definition (f(n) = g(f(n-1), f(n-2), ...)) but a naive recursion recomputes the same subproblem millions of times.
  • The state space is small and discrete (a single integer, a pair, or a (i, target) tuple) so a Map or array keyed on those arguments fits in memory.
  • Bottom-up tabulation would force you to think backwards from the base cases — top-down lets you write the recurrence in its most natural direction.
  • The problem has overlapping subproblems but you only need to evaluate the ones reachable from the entry call, not the full DP table.
  • You want to short-circuit early when a subproblem fails (cache returns false / -Infinity) instead of filling every cell of a tabulated array.

Template

function solve(n) {
  const memo = new Map();
  function dp(state) {
    if (isBase(state)) return baseValue(state);
    if (memo.has(state)) return memo.get(state);
    let best = initialValue();
    for (const next of transitions(state)) {
      best = combine(best, dp(next));
    }
    memo.set(state, best);
    return best;
  }
  return dp(n);
}

Example LeetCode problems

#ProblemDifficultyFrequency
70Climbing Stairseasyfoundational
139Word Breakmediumcompany favorite
377Combination Sum IVmediumfrequently asked
198House Robbermediumfoundational
91Decode Waysmediumcompany favorite
509Fibonacci Numbereasyfoundational
322Coin Changemediumfrequently asked
300Longest Increasing Subsequencemediumclassic

Common mistakes

  • Caching on an incomplete state — e.g. memoizing on (i) alone when the answer also depends on (i, remainingTarget). The cache then returns stale answers for different targets at the same index.
  • On Combination Sum IV, returning 0 from the base case when target overshoots zero and forgetting to return 1 at exactly target == 0 — the base case must distinguish 'no valid combination' from 'one valid combination'.
  • Using a Map keyed on stringified tuples without realizing the conversion cost dominates the recursion, making the top-down solution slower than the equivalent bottom-up table.
  • Hitting the JavaScript recursion limit (~10,000 frames on most engines) on problems like Climbing Stairs with n = 50,000 — switch to iterative bottom-up or trampoline when n is large.
  • On Word Break, recursing into every suffix without memoizing on the starting index — exponential blow-up on long strings with many small dictionary words.

Frequently asked questions

When should I use top-down memoization vs bottom-up tabulation?

Top-down is best when the recurrence is easier to write than the iteration order, when only a fraction of states are reachable, or when you want to short-circuit on a found answer. Bottom-up wins when you need every cell of the table, when stack depth would overflow, or when you want O(1) memory rolling-row optimization.

Why does adding a Map turn an exponential recursion into linear time?

Without a cache, fib(n) calls fib(n-1) and fib(n-2), each of which spawns its own subtree — O(2^n) work. With a cache, the first call to fib(k) for each k computes it once and every later call returns in O(1). The recursion visits at most n unique states, so the total work is O(n).

What is a 'state' in memoization?

The tuple of arguments that uniquely determines the subproblem's answer. For Climbing Stairs the state is the integer n. For Word Break it is the starting index i. For Combination Sum IV it is the remaining target. Identifying the minimal state is the hardest part — too few arguments cause stale-cache bugs, too many blow up the cache size.

How do I memoize a recursive function that takes multiple arguments?

Either nest Maps (memo.get(a)?.get(b)) or serialize the tuple into a string key (`${a},${b}`). String keys are simple but allocate strings on every call; nested Maps avoid that allocation at the cost of more lookup code. For tight competitive problems prefer a 2D array indexed directly by integer arguments.

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

Practice these live with InterviewChamp.AI

Drill the Memoization (Top-Down DP) pattern under real interview conditions with instant feedback on your reasoning, complexity claims, and code.

Practice these live with InterviewChamp.AI →