How to Use an LRU Cache with Lodash memoize

By default, Lodash’s _.memoize caches results indefinitely using an internal Map-like structure, which can cause significant memory leaks in long-running applications. This article demonstrates how to replace Lodash's default caching mechanism with a Least Recently Used (LRU) cache, showing both how to configure it on individual memoized functions and how to override it globally across your application.

Understanding Lodash's Cache Contract

Lodash allows you to override its cache implementation because _.memoize relies on the constructor defined at _.memoize.Cache. For an LRU cache to work with Lodash, the cache instance must implement the standard JavaScript Map interface methods:

Popular libraries such as lru-cache implement this interface directly in recent versions.

Overriding Cache for a Single Function

The safest way to use an LRU cache is on a per-function basis. After creating a memoized function, you can directly reassign its .cache property to an instance of your LRU cache before invoking it.

import _ from 'lodash';
import { LRUCache } from 'lru-cache';

function expensiveComputation(id) {
  // Complex operation here
  return { id, computedAt: Date.now() };
}

// 1. Memoize the function normally
const memoizedComputation = _.memoize(expensiveComputation);

// 2. Replace the instance's default cache with an LRU cache
memoizedComputation.cache = new LRUCache({
  max: 100, // Store a maximum of 100 items
  ttl: 1000 * 60 * 5 // Optional: Time to live of 5 minutes
});

// 3. Use as normal
memoizedComputation('user_1');
memoizedComputation('user_1'); // Served from LRU cache

Overriding the Cache Globally

If you want every memoized function created by _.memoize to use an LRU cache by default, you can override _.memoize.Cache. Because _.memoize calls new _.memoize.Cache() without arguments, you should create a wrapper class that predefines your default LRU cache configuration.

import _ from 'lodash';
import { LRUCache } from 'lru-cache';

// Create an adapter class with default LRU options
class DefaultLRUCache extends LRUCache {
  constructor() {
    super({
      max: 500 // Maximum items for any memoized function
    });
  }
}

// Globally override the default cache constructor
_.memoize.Cache = DefaultLRUCache;

// Any new memoized function now uses the LRU cache automatically
const memoizedUserData = _.memoize(fetchUserData);
const memoizedSettings = _.memoize(fetchSettings);

console.log(memoizedUserData.cache instanceof LRUCache); // true

Handling Complex Argument Keys

By default, _.memoize uses the first argument passed to the function as the cache key. When passing objects or multiple arguments, provide a resolver function to avoid cache collisions within your LRU cache:

const memoizedCalculate = _.memoize(
  (category, options) => performCalculation(category, options),
  (category, options) => `${category}:${JSON.stringify(options)}`
);

memoizedCalculate.cache = new LRUCache({ max: 50 });