JavaScript Memoization

Memoization is the advanced concept of JavaScript language. To understand this concept we need to understand function and method very well. Functions and methods are the backbones of any programming language but in JavaScript, those play a very important role. As we know in JavaScript, we have two methods to create a function or method.


// function by declaration.
function msg () {}
// function by expression.
var msg = function () {};

But the interesting thing is that we have 3 ways to invoke or call to JavaScript functions –


// Normal invoke
function msg () {}
msg()

// Self invoke
(function msg() {}());
msg.call();

// apply, call, bind
msg.apply();
msg.call();
msg.bind()();

We have already discussed how the JavaScript function works. But when we invoke the function; they recalculate all operations related to the function. A lot of time that operation is ignored by developers and the consequence of that ignorance is that our application time complexity increases.

But we can reduce that kind of recalculation thing using memoization. Now if you are thinking about what is memoization then it’s just a caching method by which we can reduce a lot of time-consuming recalculation. But before going deep in this topic we will recommend you should learn function, callbacks, apply, call, bind, and closure in JavaScript.

Let’s Understand the basics –


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

console.log(add(1, 2)); // 3
console.log(add(1, 2)); // 3

Here we invoked add function two times and we are getting 3 both times and nothing special in this code. But own thing you should notice here add function have the same value both time and add function recalculates add operation multiple times. In this example, I thought you are not understanding what time benefit can be achieved using cache but I promise at the end you realize it will be awesome.

Now time understand another example of how to handle those situations –


var cache = {};
function add(a, b) {
    var key = a + ":" + b;
    if (cache[key]) { // if already cache
        return cache[key];
    } else {
        var op = a + b; // opration
        cache[key] = op;
        return cache[key];
    }
}

console.log(sum(10, 20)); // 30 without cache
console.log(sum(10, 20)); // 30 cache

Now it’s a basic version of cache here we are using a global cache object and that is not a good practice. So now it’s time to understand the advanced version of this example for that you should basic understanding of closure.


function add() {
    var cache = {};
    return function(a, b) { 
        var key = a + ":" + b;
        if (cache[key]) { // if already in cache
    	    return cache[key];
        } else {
            var op = a + b; // opration
            cache[key] = op;
            return cache[key];
        }
    }
}
var sum = add();
console.log(sum(50, 40)); // 90 without cache
console.log(sum(50, 40)); // 90 form cache 

Now cache is not global anymore so we don’t need to worry about the cache variable. Now we need to understand the practical use of that.


function getElements( name ) { 
    var cache = {};
    return function() {
        var results; 
        if ( cache[name] ) { // if already in cache
            results = cache[name]; 
        } else { //first-time store all elements inside a cache
            results = document.getElementsByTagName(name); 
            cache[name] = results; 
        } 
        return cache[name];
    } 
}

var getEl = getElements("div")();  // without cache
var getEl1 = getElements("div")(); // cache

This kind of example might be very useful for caching of dom element and it could be time-consuming so we can also use this kind of caching snippet.

For the most advanced Use case, we can create a utile function you can modify according to what you


function memoize(fun) {
    var cache = {};
    var memoize =  function () {
        var args = [].slice.call(arguments, 0);
        var address = args.join(":");
        if (cache[address] != undefined ) {
            return cache[address]
        } else {
            var result = fun.apply(this, arguments);
            cache[address] = result
            return cache[address];
        }
    }

    return memoize;
}

var add = memoize(function(a, b) { return a + b; });

var fac = memoize(function factorial(n) {
  if (n < 0) return;
  if (n < 2) return 1;
  return n * factorial(n - 1);
});

add(20, 30); // 50 without cahce
add(20, 30); // 50 cache

fac(5); // 120 without cahce
fac(5); // 120 cache

Calculating the factorial of any number can be time-consuming if that kind of operation is inside the loop then it can slow down our application.

Key points

  1. Memoization can enhance performance by using caching the previous result of function calls.
  2. Caching uses key-value pairs so we can’t perform the same operation twice.
  3. If a cache does not exist then the operation will perform.
  4. If cache exists then we will get the cache value for that operation which saves computation time.
  5. Underscore and lodash library have their own memoize method for Memoization.

About the Author: Pankaj Bisht