Custom Cache Key Resolver in Lodash Memoize
Lodash provides the _.memoize utility to optimize
performance by caching the results of expensive function calls. By
default, _.memoize determines its cache key using only the
very first argument passed to the memoized function. This article
explains how to override this default behavior by implementing a custom
cache key resolver function, enabling proper caching for functions that
accept multiple arguments or complex data types.
The Default Resolver Limitation
When using _.memoize(func), Lodash applies a default
resolver that returns the first parameter (args[0]). If
your target function relies on two or more parameters, calls sharing the
same first argument will incorrectly return the cached result of the
initial call, ignoring any subsequent arguments:
const add = (a, b) => a + b;
const memoizedAdd = _.memoize(add);
memoizedAdd(2, 3); // Returns 5 (computed)
memoizedAdd(2, 5); // Returns 5 (incorrect! Cache hit based only on '2')Implementing a Custom Resolver
The _.memoize method accepts an optional second
argument: resolver. The resolver is a function that
receives all arguments passed to the memoized function and must return a
value (usually a string or primitive) to be used as the internal cache
key.
_.memoize(func, [resolver])1. Combining Primitive Arguments
For functions that take multiple primitives (such as numbers or strings), you can join the arguments using a delimiter or template literals:
const add = (a, b) => a + b;
// Custom resolver combining both arguments
const memoizedAdd = _.memoize(
add,
(a, b) => `${a}_${b}`
);
memoizedAdd(2, 3); // Returns 5 (computed and cached under '2_3')
memoizedAdd(2, 5); // Returns 7 (computed and cached under '2_5')2. Handling Dynamic Argument Lengths
If your function accepts a variable number of arguments, you can use
the rest operator along with JSON.stringify or
Array.prototype.join:
const multiplyAll = (...numbers) => numbers.reduce((acc, n) => acc * n, 1);
const memoizedMultiply = _.memoize(
multiplyAll,
(...args) => JSON.stringify(args)
);
memoizedMultiply(2, 3, 4); // Returns 24 (cached under '[2,3,4]')
memoizedMultiply(2, 3); // Returns 6 (cached under '[2,3]')3. Caching Based on Object Properties
When functions accept objects, generating a key from unique
identifiers (such as an id) avoids serializing the entire
object:
const fetchUserProfile = (user, options) => {
// Expensive operation
return `${user.name} - ${options.format}`;
};
const memoizedFetch = _.memoize(
fetchUserProfile,
(user, options) => `${user.id}:${options.format}`
);If the object does not have a unique identifier,
JSON.stringify(args) can be used, provided the object has
no circular references and property order remains consistent.
Customizing the Cache Store
The generated key is stored in the function's .cache
property, which by default is an instance of
_.memoize.Cache (a Map-like object). You can
inspect or modify cached entries directly using your custom key:
// Check if a key exists
memoizedAdd.cache.has('2_3'); // true
// Clear a specific entry
memoizedAdd.cache.delete('2_3');
// Clear the entire cache
memoizedAdd.cache.clear();