Replace Lodash Memoize Cache for Memory Management
Lodash's _.memoize function caches computed results
indefinitely using an unbounded internal map, which can quickly lead to
memory leaks in long-running applications. This article demonstrates how
to swap out Lodash's default cache constructor with custom,
memory-efficient data structures—such as Least Recently Used (LRU) or
fixed-size caches—both globally across your entire application and
locally on individual memoized functions.
The Lodash Cache Contract
To replace Lodash’s internal cache, your custom cache class must
implement a Map-compatible interface. Lodash expects the
following methods:
has(key): Returns a boolean indicating whether an entry exists.get(key): Retrieves the cached value for the key.set(key, value): Stores the value and returns the cache instance.delete(key): Removes the entry associated with the key.clear(): Flushes all entries (recommended for clean teardowns).
Replacing the Cache Globally
By default, _.memoize.Cache points to the native
Map constructor (or Lodash's polyfill). Assigning a new
constructor directly to _.memoize.Cache alters the default
caching mechanism for all subsequent _.memoize calls.
const _ = require('lodash');
// Define a bounded cache to prevent unbounded growth
class LimitedCache {
constructor(limit = 100) {
this.limit = limit;
this.map = new Map();
}
has(key) {
return this.map.has(key);
}
get(key) {
return this.map.get(key);
}
set(key, value) {
// Evict the oldest key if limit reached
if (this.map.size >= this.limit && !this.map.has(key)) {
const oldestKey = this.map.keys().next().value;
this.map.delete(oldestKey);
}
this.map.set(key, value);
return this;
}
delete(key) {
return this.map.delete(key);
}
clear() {
this.map.clear();
}
}
// Globally replace the internal cache constructor
_.memoize.Cache = LimitedCache;
// Usage: creates an instance of LimitedCache under the hood
const computeSquare = _.memoize((n) => n * n);Replacing the Cache per Function Instance
If you only want custom memory management on specific memory-heavy
operations, instantiate the function with _.memoize and
replace its .cache property before invocation:
const _ = require('lodash');
function expensiveTransformation(data) {
// Heavy computation
return Object.keys(data).length;
}
const memoizedTransform = _.memoize(expensiveTransformation);
// Override the cache on this specific instance
memoizedTransform.cache = new LimitedCache(50);Using Third-Party LRU Libraries
For production environments, integrating specialized libraries like
lru-cache provides robust eviction policies based on item
age or memory footprint.
const _ = require('lodash');
const { LRUCache } = require('lru-cache');
class LodashLRUAdapter {
constructor() {
this.lru = new LRUCache({
max: 500, // Maximum items
ttl: 1000 * 60 * 5, // Expire items after 5 minutes
});
}
has(key) {
return this.lru.has(key);
}
get(key) {
return this.lru.get(key);
}
set(key, value) {
this.lru.set(key, value);
return this;
}
delete(key) {
return this.lru.delete(key);
}
clear() {
this.lru.clear();
}
}
// Set globally
_.memoize.Cache = LodashLRUAdapter;Replacing _.memoize.Cache ensures deterministic memory
consumption by enforcing maximum size limits and automatic garbage
collection of unused keys, eliminating one of the most common causes of
JavaScript heap exhaustion.