On this page
What Is Memoization (and When Does It Actually Help)?
Memoization caches a function's results by its arguments so repeat calls skip the work. Learn when it's a huge win and when it's wasted effort.
Quick answer
- Memoization stores a function’s return values keyed by its arguments, so the same call returns instantly on repeat.
- It’s a big win for expensive pure functions called repeatedly with the same inputs — and useless elsewhere.
- It only works for pure functions: same input always gives same output, no side effects.
How does memoization work?
The function keeps a cache: a dictionary mapping input arguments to computed results. On each call it checks the cache first; if the arguments are there, it returns the stored value without running the body; if not, it computes, stores, and returns. Python’s functools.lru_cache does this with one decorator, including eviction of least-recently-used entries so the cache can’t grow without bound. The savings are dramatic for recursive or repeated computations: Fibonacci without memoization is exponential; with it, linear.
When is memoization the right tool?
When three things are true: the function is pure (same inputs always produce the same output, no side effects like writes or external calls), it’s expensive (seconds of compute, not microseconds), and it gets called repeatedly with the same arguments — common in recursive algorithms, repeated API data lookups, and rendering pipelines. If any condition fails, memoization is the wrong tool: caching a function with side effects returns stale results, and caching a cheap function adds overhead for nothing.
Where does memoization stop being enough?
For single-process, in-memory needs, lru_cache is the whole answer. When results must be shared across processes or machines — a web app with many workers — the cache must live outside the process: Redis or a CDN. That’s the jump from memoization to general caching: same idea (store results keyed by inputs), different storage. The migration path is usually: memoize in-process first, then move the cache out when you need cross-process sharing or persistence.
from functools import lru_cache\n\n@lru_cache(maxsize=128)\ndef compute_report(date: str) -> dict:\n # expensive pure computation, same date -> same result\n ...\n\ncompute_report("2026-08-16") # runs\ncompute_report("2026-08-16") # returns from cacheWhere this bites vibecoders
AI assistants sprinkle @lru_cache and useMemo around freely — sometimes brilliantly, sometimes pointlessly. The vibecoder failure mode isn’t missing memoization; it’s applying it blindly: caching a function that reads a database (stale data), or memoizing a cheap call (overhead with no payoff). The useful habit is checking the three conditions — pure, expensive, repeated — before accepting the assistant’s caching suggestion, and preferring to fix the underlying repeated work when the function isn’t pure.
Where AI coding assistants get this wrong
- Memoizing impure functions (reads a DB, hits an API, writes somewhere) and serving stale results.
- Applying memoization to cheap calls where the cache lookup costs more than the work.
- Unbounded caches that grow forever in long-running processes (maxsize matters).
- Memoizing per-request objects in a multi-tenant app, leaking one user’s data to another.
Checklist
- Memoize only pure, expensive functions called repeatedly with the same inputs.
- Set a bound on cache size (maxsize) to prevent unbounded growth.
- Use process-external caching (Redis, CDN) when results must be shared or persist.
- Verify freshness: invalidate or key on everything that changes the result.
FAQ
What is the difference between memoization and caching?
Memoization is caching applied to function calls — keyed by arguments, usually in-process, often automatic via decorators. Caching is the broader idea: storing any computed result (pages, queries, files) in any store, keyed by anything. Memoization is the function-level subset.
Does memoization use a lot of memory?
Only as much as the cached results, bounded by maxsize with lru_cache. Each cached result holds its arguments and return value in memory for the process’s lifetime. That’s why unbounded memoization is dangerous and why maxsize is the right default — eviction keeps memory flat.
Related topics
- What Is Caching (and the Most Common Ways to Get It Wrong)?
- How to Add Redis Caching to Your App
- What Is a Code Smell?
- What Is Database Indexing (and Why Is My Query Slow)?
- What Is Continuous Delivery?