Lodash Memoize with Multiple Arguments

By default, the Lodash _.memoize function only utilizes the first argument passed to a memoized function to create its cache key, ignoring any subsequent arguments. When memoizing functions that accept multiple parameters, this behavior causes inaccurate cache hits and stale returns. To correctly cache results based on all supplied inputs, you must pass a custom resolver function as the second argument to _.memoize that serializes or maps every argument into a unique identifier.

The Default Behavior Issue

Consider a function that adds two numbers:

const add = (a, b) => a + b;
const memoizedAdd = _.memoize(add);

memoizedAdd(1, 2); // Returns 3, caches under key: 1
memoizedAdd(1, 5); // Returns 3, because cache key 1 already exists

Because Lodash defaults to using arguments[0] as the cache key, memoizedAdd(1, 5) mistakenly returns the cached result of memoizedAdd(1, 2).

Implementing a Custom Resolver

The _.memoize function accepts a second argument, resolver, which receives all arguments passed to the memoized function:

_.memoize(func, [resolver]);

The resolver must return a single value (typically a primitive like a string) to act as the cache key in Lodash's internal Map.

1. The JSON.stringify Approach

The most common and versatile solution is to serialize the entire argument list into a JSON string using JSON.stringify:

const add = (a, b) => a + b;

const memoizedAdd = _.memoize(
  add,
  (...args) => JSON.stringify(args)
);

memoizedAdd(1, 2); // Caches under key: "[1,2]" -> Returns 3
memoizedAdd(1, 5); // Caches under key: "[1,5]" -> Returns 6

This approach works effectively for primitives, plain arrays, and serializable objects.

2. Delimited String Joining

For functions that strictly receive primitive values (such as numbers or clean strings), converting the argument array directly to a joined string offers better performance than JSON.stringify:

const getUserPermissions = (userId, role) => {
  // Expensive lookup
  return `${userId}:${role}`;
};

const memoizedPermissions = _.memoize(
  getUserPermissions,
  (userId, role) => `${userId}__${role}`
);

When using delimiters, ensure the chosen separator cannot appear within the input values themselves to prevent key collisions (for example, inputs ('a_b', 'c') and ('a', 'b_c') would both produce 'a_b_c' if using an underscore delimiter).

Considerations and Limitations

When creating a custom resolver for multiple arguments, consider the following constraints: