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:

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.