~ / track D / fp foundations

Closures and memoization

Intermediate

A closure is a function that "closes over" the bindings it can see at the moment it is created — those bindings travel with the function even after the surrounding scope is gone. Memoization is a small but powerful application of that idea: a wrapper function carries a cache of past results in its closure, so repeated calls with the same arguments skip the work.

Minimal example

Every fn is a closure. Here the returned function remembers n long after adder has returned:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

The two adders share the same code but carry different captured n values — that's the whole point of a closure.

Practical example: a counter factory

Closures shine when you want to bundle behavior with private state. Here the atom lives inside the closure, so each call to make-counter produces an independent counter:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

c1 and c2 can't see each other's atom — encapsulation, built out of nothing but a function and a let.

Memoization

memoize takes a function and returns a new function that caches results by argument tuple. Two rules to keep in mind: the function must be pure (same inputs → same output, no side effects), and the cache grows without bound, so don't memoize functions with huge or unbounded input spaces.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

A classic demo is the naive Fibonacci, whose exponential recursion collapses to linear when memoized:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Notice the recursive calls go through the memoized fib, so each subproblem is solved at most once.

When memoization is the wrong tool

  • Impure functions (I/O, randomness, time-dependent results) will return stale or wrong values from the cache.
  • Cheap functions rarely benefit — the lookup itself costs something.
  • Unbounded input domains turn the cache into a memory leak; reach for core.cache or core.memoize if you need eviction policies.

Check yourself

? quiz

Why does `memoize` only work safely on pure functions?

Exercise

Write make-adder so that ((make-adder 7) 3) returns 10. Then write make-bounded-counter that takes a maximum value and returns a function that increments a private counter but stops at max.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate
 status: new