How Lodash Memoize Handles Garbage Collection
The _.memoize function in the Lodash JavaScript library
does not automatically garbage collect cached results, which can lead to
memory retention issues if left unmanaged. By default, Lodash retains
cached values using strong references in a standard map-like structure,
meaning entries remain in memory for the lifetime of the memoized
function. To enable garbage collection or prevent memory leaks,
developers must either manually clear the cache or replace Lodash's
default cache constructor with alternative implementations such as
WeakMap or a Least Recently Used (LRU) cache.
The Default Cache Mechanism
By default, Lodash’s _.memoize instantiates an internal
cache using JavaScript's native Map (or a fallback object
structure in older environments), assigned via
_.memoize.Cache.
const _ = require('lodash');
const compute = (obj) => { /* heavy computation */ };
const memoizedCompute = _.memoize(compute);When an argument is evaluated, Lodash stores the resolved cache key and its corresponding result inside this cache instance:
- Strong References: Standard
Mapobjects maintain strong references to both keys and values. - Preventing GC: Because the references remain active
inside the memoized function's
.cacheproperty, the JavaScript garbage collector cannot sweep either the arguments or the returned values, even if they are no longer needed elsewhere in the application. - Unbounded Growth: Each unique key generates a new entry. In long-running Node.js processes or single-page applications, calling a memoized function with continuously varying inputs creates an unbounded cache, causing memory consumption to grow steadily over time.
Enabling Garbage Collection with WeakMap
If your cached computations rely exclusively on object keys, you can
integrate garbage collection directly by overriding
memoize.Cache with a WeakMap.
_.memoize.Cache = WeakMap;
const processUserData = _.memoize((user) => {
return { id: user.id, computedProfile: expensiveTransform(user) };
});Using a WeakMap fundamentally changes how garbage
collection behaves:
- Weak Key References: A
WeakMapholds "weak" references to its keys. If there are no other references to the key object anywhere else in your program, the key—and its associated value in the memoize cache—becomes eligible for garbage collection. - Limitations:
WeakMapkeys must be objects or non-registered symbols; primitive values like strings or numbers cannot be used as keys. Additionally,WeakMapis not iterable, meaning you cannot inspect the entire cache or check its size.
Manual Memory Management
For scenarios where WeakMap is impractical—such as when
caching results based on primitive values—you must manage memory
manually using the .cache property exposed on the memoized
function.
- Deleting Specific Entries: You can remove
individual entries to allow garbage collection of specific items:
memoizedCompute.cache.delete(cacheKey); - Clearing the Entire Cache: When a context or
lifecycle finishes, you can flush all entries entirely:
memoizedCompute.cache.clear();
Using an Eviction Strategy (LRU Cache)
To maintain bounded memory usage without manual clearing, you can replace the default cache constructor with a Least Recently Used (LRU) cache. This limits the maximum number of items held in memory.
const LRU = require('lru-cache');
// Configure a custom cache factory
_.memoize.Cache = function() {
return new LRU({ max: 500 });
};
const cachedFunction = _.memoize(expensiveCalculation);When the item limit is reached, the LRU cache automatically evicts the oldest unused items. Once evicted from the cache, those items lose their strong references and are automatically cleaned up by the JavaScript engine during the next garbage collection cycle.