TABLE OF CONTENTS

Javascript’s Lazy vs Eager Function Execution

Author Sagi Liba
Sagi Liba on Jul 22, 2020
5 min 🕐

Here’s a question for you, the code below contains a function that repeats the letter “A” 10 times, can you tell if its an eagerly executed function or a lazily executed function?

Copy
function repeater(count) {
return function allTheAs() {
return "".padStart(count, "A");
};
}
var A = repeater(10);
A(); // Outputs: "AAAAAAAAAA"

We wrapped the function allTheAs that’s actually doing the work inside the repeater function, therefore the work won’t actually run until we call the function that repeater returns.

Calling the function A() does the work lazily. the concept is not hard but lets go over it to fully understand eager & lazy execution.

Why would we decide to call a function lazily?

PRO: The function might do some expensive computations and we are not sure that the function will ever be called, a good solution would be to run it lazily.

CON: The downside of deferring the execution is every time we call the function it will do the expensive computation over and over again.

Here’s the opposite side of doing lazy work, we can change the function to work eagerly.

Copy
function repeater(count) {
var str = "".padStart(count, "A");
return function allTheAs() {
return str;
};
}
var A = repeater(10);
A(); // Outputs: "AAAAAAAAAA"
A(); // Outputs: "AAAAAAAAAA"

Now when we call the repeater function we are immediately doing the work. the variable str immediately calculates the string “AAAAAAAAAA” when calling the function repeater.

PRO: After we did it once we don’t have to do it ever again.

CON: if this was also an expensive computation the downside would be a situation where the function never gets called, therefore we wasted time doing expensive computation that we won’t ever use.

Having the benefits of both worlds

I’ve got another question for you, is there a way for us to change the code so that the function would do the work only once and we don’t have to do the work unless its been asked for?

Well here’s a happy medium between the two approaches:

Copy
function repeater(count) {
var str;
return function allTheAs() {
if (!str) {
str = "".padStart(count, "A");
}
return str;
};
}
var A = repeater(10);
A(); // Outputs: "AAAAAAAAAA"
A(); // Outputs: "AAAAAAAAAA"

Now we are deferring the work until we decide to execute the function when calling A() the first time and we won’t have to do the work ever again like when calling A() the second time because we have cached the result inside the inner variable str.

Functional Programming Aspects

We’ve closed over a function that has a dynamic variable called str (red functional programming flags going off), at the beginning the variable str is undefined and later we change its value to cache the result.

Take a second and think, are you sure that this code is still functionally pure?

Alarm

If you’re not sure let me help you, the answer is found at the last call to A(), you should ask yourself how many time will calling A() will return the exact same string of “AAAAAAAAAA”?

well every time, therefore its functionally pure.

The code is not obviously pure but its still functionally pure. To make it more obvious that it’s pure to the programmer reading this function we could help him be more certain about it by using a declarative style.

Copy
function repeater(count) {
return memoize(function allTheAs() {
return "".padStart(count, "A");
});
}
var A = repeater(10);
A(); // Outputs: "AAAAAAAAAA"
A(); // Outputs: "AAAAAAAAAA"

In the above code we’ve used a function called memoize, this utility adds a cache to the wrapped function, it makes the code more obviously pure because we are not closing over any variable that gets reassigned.

By caching the result we benefit from eager execution and lazy execution.

A thought that might have crossed your mind is that we’ve also improved the performance of the function because we won’t have to recalculate it again so you might think we should memoize every function to increase the performance of all your function calls.

This might sound like a good solution but you must take into consideration the memory aspect of caching a functions result. when we cache a result we save it inside the internal memory of the function, that result is occupying more space that the application must maintain.

Before memoizing a function you must ask yourself:

  • How many times will this function be called?
  • Will it be called with the same input?

If the answer is that the function will be called many times with the same input then you should consider using memoization for its performance benefits.

If its going to be called many times with different inputs we are not gaining any benefits by saving the results in the cache, therefore this is not a good use for memoization because we would use up a lot of memory.

Let’s finish with a clear definition of what is a pure function call

A pure function call is a function that we can take its returned value and replace any other call to that function with that value without breaking any of our application code.

if we could just take the string “AAAAAAAAAA” and literally replace the function call with that value and it won’t affect the rest of our application, then its pure.

This article was based upon a part of a course by Kyle Simpson named Functional-Light JavaScript, v3 at Frontend Masters. I highly recommend Kyle Simpson’s online courses and Frontend Masters to drastically improve your javascript’s skills.

The code used in this article was also taken from the course.

Happy Coding!

© 2020-present Sagi Liba. All Rights Reserved