The correct way to use Memoize in ES6 Node

Memoize is one of the most useful functions to improve performance. Here we cover what it is, when to use it and how to use it. I wanted to cover some incorrect but common usage of it aswell.

What is it:

Memoize is a function which caches output of a function based on input. There are lodash implementations as well as isolated NPM modules.
For e.g

memoize(someFunction)(someArgument);

So it saves on processing if the same argument is handed.
It works with a cache store only executing the function if a value for that argument (key) is not found, that value and key are then cached for future usage.
I imagine the internal implementation to be something like:

function memoize(func) {
  var result = {};
  return function(...args) {
      var key = [...args].reduce(function(prev, curr){ 
        return prev + JSON.stringify(curr);
      }, 'undefined-');
      if (!result.hasOwnProperty(key)) { 
        result[key] = func(...args); 
      }
      return result[key];
  };
}

Its important to note it only works for pure functions i.e those not relying on Globals or side-effects/outside influence.
E.g

add(a, b) {
   return a + b;
}

When to use it:

It is incredibly useful for functions which do heavy lifting but have common arguments regardless of the request.
E.g

parseUrl(request.hostname)

Now it is quite possible for a lot of users of this product to come from the same hostname, so it’s inefficient to run a function involving hostname parsing for every single request, especially if the result is the same.

This can have a big impact on performance, so Memoize is often deployed as a first action after noticing spikes while profiling an app (see my previous article on that subject and reading Flame graphs).

How to use:

I often see it used in the below manner. Called by some middleware..

function getHostnameValue() {
  const parsedUrl = memoize(parseUrl)(request.hostname);
  return parsedUrl.value;
}

So the cache store lives within a single reference, new references are creating different cache stores.
So based on that this is ONLY useful if the reference ‘parsedUrl‘ is called multiple times per request. It is not in this example so here it is likely hurting performance more than it is helping. There is a new reference created for each request and the store is never serving its purpose.
The proper way to use it is

const parsedUrl = memoize((hostname) => {
  return parseUrl(hostname);
});
function getHostnameValue () {
  return parsedUrl(request.hostname).value;
}

This sets the memoized function at compile-time (starting of node server) and every request will use the same instance at run-time as its in the modules global scope. Serving as a singleton.
This means we can use a shared cache store and there will certainly be performance gains.
Try it for yourselves.

I hope this article has served as a useful resource for optimised Memoize usage.

Feel free to comment or email me if you want to chat or feel there is a mistake anywhere.

Leave a Reply