How to use Memoize to cache JavaScript function results and speed up your code

August 21, 2017 (7y ago)

Functions are an integral part of programming. They help add modularity and reusability to our code.

It’s quite common to divide our program into chunks using functions which we can call later to perform some useful action.

Sometimes, a function can become expensive to call multiple times (say, a function to calculate the factorial of a number). But there’s a way we can optimize such functions and make them execute much faster: caching.

For example, let’s say we have a function to return the factorial of a number:

function factorial(n) {
  // Calculations: n * (n-1) * (n-2) * ... (2) * (1)
  return factorial;
}

Great, now let’s find factorial(50). The computer will perform calculations and return us the final answer, sweet!

When that’s done, let’s find factorial(51). The computer again performs a number of calculations and gets us the result, but you might have noticed that we’re already repeating a number of steps that could have been avoided. An optimized way would be:

factorial(51) = factorial(50) * 51

But our function performs the calculations from scratch every time it’s called:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Wouldn’t it be cool if somehow our factorial function could remember the values from its previous calculations and use them to speed up the execution?

In comes memoization, a way for our function to remember (cache) the results. Now that you’ve a basic understanding of what we’re trying to achieve, here’s a formal definition:

Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again

Memoizing in simple terms means memorizing or storing in memory. A memoized function is usually faster because if the function is called subsequently with the previous value(s), then instead of executing the function, we would be fetching the result from the cache.

Here’s what a simple memoized function might look like (and here’s a CodePen in case you want to interact with it):

// a simple function to add something
const add = (n) => n + 10;
add(9);

// a simple memoized function to add something
const memoizedAdd = () => {
  let cache = {};
  return (n) => {
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    } else {
      console.log('Calculating result');
      let result = n + 10;
      cache[n] = result;
      return result;
    }
  };
};

// returned function from memoizedAdd
const newAdd = memoizedAdd();
console.log(newAdd(9)); // calculated
console.log(newAdd(9)); // cached

Memoization takeaways

Some takeaways from the above code are:

Writing your own memoize function

The previous code works fine but what if we wanted to turn any function into a memoized function?

Here’s how to write your own memoize function (codepen):

// a simple pure function to get a value adding 10
const add = (n) => n + 10;
console.log('Simple call', add(3));

// a simple memoize function that takes in a function
// and returns a memoized function
const memoize = (fn) => {
  let cache = {};
  return (...args) => {
    let n = args[0]; // just taking one argument here
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    } else {
      console.log('Calculating result');
      let result = fn(n);
      cache[n] = result;
      return result;
    }
  };
};

// creating a memoized function for the 'add' pure function
const memoizedAdd = memoize(add);
console.log(memoizedAdd(3)); // calculated
console.log(memoizedAdd(3)); // cached
console.log(memoizedAdd(4)); // calculated
console.log(memoizedAdd(4)); // cached

Now that’s great! This simple memoize function will wrap any simple function into a memoized equivalent. The code works fine for simple functions and it can be easily tweaked to handle any number of arguments as per your needs. Another alternative is to make use of some de-facto libraries such as:

Memoizing recursive functions

If you try passing in a recursive function to the memoize function above or _.memoize from Lodash, the results won’t be as expected since the recursive function on its subsequent calls will end up calling itself instead of the memoized function thereby making no use of the cache.

Just make sure that your recursive function is calling the memoized function. Here’s how you can tweak a textbook factorial example (codepen):

// same memoize function from before
const memoize = (fn) => {
  let cache = {};
  return (...args) => {
    let n = args[0];
    if (n in cache) {
      console.log('Fetching from cache', n);
      return cache[n];
    } else {
      console.log('Calculating result', n);
      let result = fn(n);
      cache[n] = result;
      return result;
    }
  };
};

const factorial = memoize((x) => {
  if (x === 0) {
    return 1;
  } else {
    return x * factorial(x - 1);
  }
});

console.log(factorial(5)); // calculated
console.log(factorial(6)); // calculated for 6 and cached for 5

A few points to note from this code:

Is memoization same as caching?

Yes, kind of. Memoization is actually a specific type of caching. While caching can refer in general to any storing technique (like HTTP caching) for future use, memoizing specifically involves caching the return values of a function.

When to memoize your functions

Although it might look like memoization can be used with all functions, it actually has limited use cases:

Further reading

The following links can be useful if you would like to know more about some of the topics from this article in more detail:

I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript.