Implementing a Custom _.memoize() Function in JavaScript



The _.memoize method in Lodash is used to optimize functions by caching the results of function calls. When the memoized function is called with the same arguments, it returns the cached result instead of recalculating it.

Let’s implement a custom version of the _.memoize() function.


What is _.memoize()?

The _.memoize() function is used to speed up the performance of functions by caching the results of expensive computations. When the function is called again with the same arguments, the cached result is returned, thus avoiding redundant calculations.

Real Interview Insights

Interviewers might ask you to:

  • Implement a function that mimics the behavior of Lodash’s _.memoize.
  • Handle cases with different argument types and complex objects.
  • Ensure that the memoization function can be customized for specific use cases.

Implementing customMemoize Function

Here’s how you can implement a custom memoize function:

function customMemoize(func, resolver) {
  const cache = new Map();
 
  return function(...args) {
    const key = resolver ? resolver(...args) : args[0];
    
    if (cache.has(key)) {
      return cache.get(key);
    }
 
    const result = func.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
Explanation:
  • Cache: We use a Map to store the results of function calls. The Map object allows us to associate a specific key with a result, making it easy to check if a result for a given set of arguments already exists.
  • Resolver: The resolver function allows customization of the cache key. If no resolver is provided, the first argument is used as the key by default.
  • Memoization Logic: When the memoized function is called, we first check if the result for the given key is already in the cache. If it is, we return the cached result. Otherwise, we call the original function, store the result in the cache, and return it.

Practical Examples

Let's see the customMemoize function in action:

// A simple function that returns the square of a number
const square = customMemoize((n) => {
  console.log('Calculating square of', n);
  return n * n;
});
 
console.log(square(4)); // Output: Calculating square of 4 \n 16
console.log(square(4)); // Output: 16 (cached result)
console.log(square(5)); // Output: Calculating square of 5 \n 25
 
// Using a custom resolver to memoize a function that takes multiple arguments
const add = customMemoize((a, b) => a + b, (a, b) => `${a}-${b}`);
 
console.log(add(1, 2)); // Output: 3
console.log(add(1, 2)); // Output: 3 (cached result)
console.log(add(2, 3)); // Output: 5

Handling Edge Cases

  1. Complex Argument Types: Ensure that the function handles complex argument types by providing a custom resolver.
  2. Cache Size: Consider the implications of an ever-growing cache. In real-world scenarios, you might want to implement a cache limit or eviction strategy.
  3. Invalid Keys: Handle cases where the resolver returns undefined or null as the key.

Use Cases for _.memoize()

  1. Expensive Calculations: Memoize functions that perform expensive operations, like recursive calculations or data processing.
  2. Pure Functions: Apply memoization to pure functions, where the output depends solely on the input and has no side effects.
  3. API Calls: Cache results of API calls where the response is deterministic based on the input.