The memoize method from Lodash and Underscore.js wraps a callback and returns a memoized function. By default, that memoized function uses only the first argument received by the wrapped callback as the cache key unless an explicit cache key function is provided.

Why is this an issue?

Memoization is safe only when the cache key captures every input that can change the result. In Lodash and Underscore.js, the default memoize behavior is easy to miss: when no function is provided to compute the cache key, the cache is keyed only by the first argument received by the wrapped callback.

When a memoized function has several parameters, later parameters are ignored by the cache unless the cache key function includes them. This default behavior can make the caching strategy harder to understand and review at the call site. If later parameters can change the result, a later call with the same first callback argument but different additional arguments can also return a stale result computed for a previous call. Even when the function is intentionally keyed only by the first callback argument, an explicit cache key function makes that choice visible.

How to fix it

Provide an explicit function that builds the cache key from the callback parameters that determine the result. When only the first callback argument is intentionally used as the cache key, make that choice explicit by returning that argument from the cache key function.

Code examples

Noncompliant code example

When the result depends on both markdown and theme, both parameters should contribute to the cache key.

import memoize from "lodash/memoize";

const renderMarkdownPreview = memoize((markdown, theme) => { // Noncompliant
  return markdownRenderer.render(markdown, { theme });
});

Compliant solution

import memoize from "lodash/memoize";

const renderMarkdownPreview = memoize(
  (markdown, theme) => {
    return markdownRenderer.render(markdown, { theme });
  },
  (markdown, theme) => JSON.stringify([markdown, theme])
);

If only the first callback argument intentionally identifies the result and the other arguments only provide execution context, provide a cache key function that makes that strategy explicit.

Noncompliant code example

import { memoize } from "lodash-es";

const generateThumbnail = memoize((videoId, frameRenderer) => { // Noncompliant
  return frameRenderer.generateThumbnail(videoId);
});

Compliant solution

import { memoize } from "lodash-es";

const generateThumbnail = memoize(
  (videoId, frameRenderer) => {
    return frameRenderer.generateThumbnail(videoId);
  },
  videoId => videoId
);

Resources

Documentation