The Lazy Function Declaration Pattern – Javascript

Among the many patterns in javascript, there is one called the lazy function declaration pattern. It is supposed to be an improvement over this:


var a = (function(){
    var b = [1,2,3,4];
    return function(n){
        return b[n];
    }
})();

What this does, is it creates a function and executes it immediately, and stores the return value in a. We want to do this in order to take advantage of the closure created in the process which allows us to access the values in b. This piece of code is an improvement over this:


var a = function(n){
    var b = [1,2,3,4];
    return b[n];
}

which tends to be slower because it creates a new array every time you execute the function. In this case we are only setting b, but if we were doing something more computationally expensive, then we would appreciate the performance hit even more.

The lazy pattern tries to optimize even more over that piece of code, but fails on doing so. First, lets look at the lazy pattern:


var a = function(n){
    var b = [1,2,3,4];
    a = function(n){
        return b[n];
    }
    return a(n);
}

The advantage of doing this is that we don’t need to initialize b unless we really need it. On our first example, the outer function executes immediately, and defines b, and it will define it, even if we never get to actually call the function. Again, in this example the initialization is very simple, but if we had to do more than just create b we would see why it may be important to skip doing the preparation. Think for example of a case where the initialization involves creating a bunch of new elements and adding them to the DOM tree. This could be expensive, and if we are not going to need them, why should we create them in the first place?

So far it looks like the lazy patter really is an improvement over the first example, so where does it fail?

Suppose we want to assign a to an object:


var a = function(n){
    var b = [1,2,3,4];
    a = function(n){
        return b[n];
    }
    return a(n);
}

var c = {
    d : a
}

Now, the function will never perform the optimization that it is supposed to, because we are assigning a reference the the original function, rather than the optimized version of it that takes advantage of the closure. In order to understand a bit more what is going on, lets do some testing.

Lets start by understanding exactly how the lazy pattern works.

When a is called for the first time, it performs the preparation, in our example, that preparation is initializing b. Then it replaces itself with a new shorter version, which skips the preparation and just returns the value that we want. Finally, it executes the new function. Lets change the function a little bit so we can follow as it executes:


var a = function(n){
    var b = [1,2,3];
    alert('b');
    a = function(n){
        alert(b[n]);
    }
    a(n);
}

as you can see, we have added an alert right after the declaration of b. This will help us “see” when that part of the code is executed. We also change the return in the inner function for an alert. That way we can keep an eye on when that part of the code is executed. Lastly, since the inner function doesn’t return anything, the outer function doesn’t have to return anything either, so we get rid of the last return statement. Lets now proceed to call the function:


a(1);
a(2);

As you can see, the first time we call the function we get first an alert that says “b”, which lets us know that the preparation code was executed. Then we get another alert with the value that we were accessing from b, and finally, we get another alert, but this one corresponds to the second execution of the function. In this second execution we don’t get a b alert because that part of the code is not executed again, but we can still access the values of b because of the closure that was created. This is pretty cool. And if we never call the function, the preparation, which could be expensive, is never executed, whereas on the very first example it is, regardless of whether the function is called or not. However, what happens when we assign a to an object’s method?


var a = function(n){
    var b = [1,2,3];
    alert('b');
    a = function(n){
        alert(b[n]);
    }
    a(n);
}

var c = {
    d : a
}

c.d(0);
c.d(1);

You will now notice that we get two b alerts, which lets us know that the preparation code is being executed on both function calls. This completely eliminates the performance improvement, which is why the lazy pattern is not such a good improvement over the very first example.

If we want to understand how the improvement was supposed to work, we can do so by modifying our very first example a bit:


var a = (function(){
    var b = [1,2,3,4];
    alert('b');
    return function(n){
        return b[n];
    }
})();

Make sure this code is the only thing being executed. As you can see we also added an alert. When you run this code, you get that alert, even though there is never a call to a. Compare to this:


var a = function(n){
    var b = [1,2,3];
    alert('b');
    a = function(n){
        alert(b[n]);
    }
    a(n);
}

Again, make sure this is the only code being executed. You will see that the preparation is not executed as evidenced by the lack of an alert popping out on the window. So the idea is that we can save the preparation if we don’t need it. However, we can modify the first example a bit to make sure the preparation only happens if we need it:


var a = (function(){
    var b;
    var prep_done = false;
    return function(n){
        if(!prep_done){
            b = [1,2,3,4];
            alert('b');
            prep_done = true;
        }
        alert( b[n] );
    }
})();

This way we make sure the preparation only happens if the functions is called. If you run that code alone, you won’t see an alert, and that lets us know that the preparation is not being executed. However, if we bind that function to an object’s method, we don’t see the double b alert:


var a = (function(){
    var b;
    var prep_done = false;
    return function(n){
        if(!prep_done){
            b = [1,2,3,4];
            alert('b');
            prep_done = true;
        }
        alert( b[n] );
    }
})();

var c = {
    d : a
}

c.d(0);
c.d(1);

You could argue that this new example adds the cost of an if to the function execution, but that is actually very minimal and will most likely have no real effect on the performance of the code. The lazy function definition pattern fails in that it makes the function more confusing to use and it could end up never really performing any improvements.

Note: This post was inspired by a little section on the Function the Ultimate talk by Douglas Crockford. I recommend you watch the entire talk, and the others belonging to the same series of talks.