Memoization is an optimization technique used primarily to speed up programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

It’s a form of caching where function outputs are remembered (memorized) based on their inputs. This is particularly useful in recursive algorithms or dynamic programming, where the same subproblems are solved repeatedly.

Key Characteristics

  • Used to avoid redundant computations.
  • Typically applied to pure functions (functions that always produce the same output for the same input and have no side effects).
  • Can significantly improve time complexity.
  • Involves a lookup table, dictionary, or hash map to store previous results.

Example: Fibonacci Without and With Memoization

Without Memoization (Naive Recursion)

def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
  • Time complexity: O(2ⁿ)
  • Recomputes the same values repeatedly.

With Memoization

memo = {}
 
def fib(n):
    if n in memo:
        return memo[n]
    if n <= 1:
        memo[n] = n
    else:
        memo[n] = fib(n - 1) + fib(n - 2)
    return memo[n]
  • Time complexity: O(n)
  • Saves and reuses results.

Using functools.lru_cache in Python

Python provides a built-in decorator for memoization:

from functools import lru_cache
 
@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
  • maxsize=None means unlimited cache.
  • Thread-safe and automatic cache management.

Beware though that this consumes a lot of memory and it should be avoided in production environments

Use Cases

Problem TypeExample
Recursive AlgorithmsFibonacci, factorial, permutations
Dynamic ProgrammingKnapsack, edit distance
ParsingRecursive descent parsers
Graph TraversalsMemoizing subpath results
API ResponsesCaching expensive calls

Considerations

  • Memory consumption: Storing many results can increase memory usage.
  • Side effects: Don’t memoize functions with I/O or changing state.
  • Mutable arguments: Use only with immutable argument types (e.g., strings, tuples, ints).
  • Cache invalidation: If input data changes, memoized results may become stale.