DEV Community

Cover image for Use Closures for Memory Optimizations in JavaScript (a case study)
Ahmed Murtaza
Ahmed Murtaza

Posted on

Use Closures for Memory Optimizations in JavaScript (a case study)

Beside functions being first class citizens in JavaScript, there are plenty of other features, allowing functions to make an extra mile ride. Closures are one of them.

What is a Closure?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)
Mozilla

Let's take an example:

function adder(a) {
  return function(b) {
    return a + b;
  };
}

let add5 = adder(5);
let add10 = adder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12
Enter fullscreen mode Exit fullscreen mode

From above code, it's clear how closures work, and how it remembers the lexical scope, the function was declared within. But how this could be useful, or is it just non-practical questions in JavaScript interviews?

Don't worry, we have got plenty of applications and usage, of Closures throughout JavaScript ecosystem. Today, the usage we want to discuss is how we can optimize memory usage using Closures. Consider the code below:

function multiply(y){
    let x = Math.pow(10,10);
    return x* y;
}
multiply(25); //250000000000
multiply(45); //450000000000

Enter fullscreen mode Exit fullscreen mode

It looks very straight forward, right? ... No, actually if you notice every time the multiply() function is called, the let x = Math.pow(10,10) is recreated and occupy certain memory, in this case quite a large memory for sure, due to the large numeric value it's generating.

Bringing in the Closure

What if we make it possible to create let x = Math.pow(10,10); only once, to the extent where it has been repetitive across the multiply() function calls, this is where the Closures come into play. Let's take a look at the below modified code:

function multiply(){
    let x = Math.pow(10,10);
    return function(y){
        return x* y;
    }
}
let multiplier= multiply();
multiplier(25); //250000000000
multiplier(45); //450000000000
Enter fullscreen mode Exit fullscreen mode

Here we introduced returning an inner function, which creates a closure, and does not recreate let x = Math.pow(10,10); with each multiplier() call, and hence avoid excess memory leaks. This conveys us, by using Closures we can easily avoid costly memory jobs.

So that's it for today. Hope you have benefited from this case study, or Do share if you ever faced similar scenarios and what approaches you have followed. Stay tuned for the next JS hack!😃

Top comments (25)

 
hasnaindev profile image
Muhammad Hasnain • Edited

Honestly, it doesn't really matter. He chose to use normal functions instead of arrow functions and there is nothing wrong with it. I'd argue that if I was a beginner, I wouldn't understand your code, so I actually prefer his way of doing things.

No need to be fancy as this website is targeted towards beginners. Although, I agree, a lot of actual senior and experienced developers are also choosing this platform but they don't really care about reading such articles. I hope you got my point.

Thanks.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

I get the idea, but this uses memory, not saves it. The closure is taking up memory by its definition - the thing you are saving is the processing required for Math.pow() (or **).

Now in the vanilla version the value of Math.pow(10,10) is being stored on the stack - this memory is only in use during the execution of the function and will be immediately recycled as soon as it is exited, no need for garbage collection as the result is just a number.

Collapse
 
ahmedgmurtaza profile image
Ahmed Murtaza

@miketalbot , I agree with you to the point that it saves the processing time, but without closures, everytime we run multiply(), the line 2, let x = Math.pow(10,10); recreates in memory, where as with closures, line 2 does not recreates in memory + it saves the processing time for large jobs (as you have said above).

Collapse
 
mindplay profile image
Rasmus Schultz

But Mike is right here - in terms of net memory usage, there is no difference.

The difference in terms of memory here, is not the amount of memory used, but when that memory is used and gets released. The other difference is in terms of thrashing the garbage collector - by using a closure, you avoid unnecessary thrashing, but strictly speaking, you do so by keeping this memory allocated and never releasing it.

In that sense, your optimization could be considered "worse" in terms of overall memory usage. Although it's definitely much better in terms of performance overall, the explanation for that is not that you're saving memory, which you're really not - on the contrary.

It's a good optimization, but not for the reasons you explained.

Also, with regards to this part of your explanation:

the let x = Math.pow(10,10) is recreated and occupy certain memory, in this case quite a large memory for sure, due to the large numeric value it's generating.

It's a good guess, but that is not how numbers are stored in JavaScript - the number type is a 64-bit floating point, no matter which number.

You can learn about number storage here:

2ality.com/2012/04/number-encoding...

But to begin with, you really shouldn't spend your time "optimizing" something this small. You should trust that the language does what's best. If you raise a value to a variable in a parent scope, it should be because the code makes more sense that way - the person reading the code will understand that this value doesn't change. Or because the value is expensive to calculate.

Speculating about saving 8 bytes of memory is not a good use of your time, unless you expect to have millions of instances - and even then, you would need to weigh the fact that those millions of instances can't be deallocated when they're not in use, against the performance overhead of calculating the value on demand.

If you do have a case that calls for memory optimizations, you should learn to use a profiler and get your facts from actual measurements - in your case here, you would have found that the extra closure you use for your "memory optimization" actually requires more memory, not less.

Performance, and sometimes even memory usage, is too complex in JavaScript for you to guess or assume anything - the execution model of JavaScript is extremely complex, and the measurements often not at all what you might intuitively expect.

Thread Thread
 
alrunner4 profile image
Alexander Carter

This is what I came to the comments to add. 👍

Collapse
 
miketalbot profile image
Mike Talbot ⭐

My point is that a local primitive variable in a function is allocated on the stack and not in the main heap of memory (it's different if it is an object), this memory isn't "being used" it's just an offset from the current stack pointer.

The moment we "close over" a function then the function definition is converted internally into a class and the value of that variable becomes a property of the instance of that class. The stack is a super efficient way of handling primitive value storage during the execution of a function. If x was an object then it wouldn't be the same story, as the reference to x would be stored on the stack but the contents of x would be allocated in memory and be subject to subsequent garbage collection, leading to additional processing requirements. In neither of these cases would the variable be "used" as in it wouldn't continue to take up required memory after the function exited, however as Javascript uses garbage collection it would need processing for the memory to be made available again.


function multiply(y) {
    let x = 10 ** 10
    return y * x
}

// This function does NOT allocate 10000000 copies
// of x in memory, the same location on the stack is used each time.
//  No additional memory will be consumed and
// there will be no garbage collection
for(let i = 0; i < 10000000; i++) {
    console.log(multiply(i))
}


Enter fullscreen mode Exit fullscreen mode

Consider this:


function multiply(y) {
    let x = {pow: 10 ** 10}
    return y * x.pow
}

// This function DOES allocate 10000000 copies of x in memory,
// however NO additional memory will be permanently held
// as garbage collection will free the temporary values as they are no longer
// accessible.
for(let i = 0; i < 10000000; i++) {
    console.log(multiply(i))
}


Enter fullscreen mode Exit fullscreen mode
Collapse
 
danielo515 profile image
Daniel Rodríguez Rivero

The example uses more memory indeed.
But the author also mentions that his example does not suffer from memory leaks, implying that the original one did. But there is no memory leak on the original code, memory leak means a piece of memory that it is unused and never garbage collected.
People should, at least, review what the terms they use mean before publishing an article.

Collapse
 
ahmedgmurtaza profile image
Ahmed Murtaza

Sure @lukeshiru , this would surely be more cleaner code 👍. However my attempt was to be as simple as possible, thats why tried to kept it with an old JS syntax 😊

Collapse
 
cedriking profile image
Cedrik Boudreau

I was about to comment about using Math.pow instead of just using **. Really clean example 👌

Collapse
 
pcockerell profile image
Peter Cockerell

I think examples, no matter how trivial, should show code that people would actually write. Why would anyone use pow() (or **) to create the constant 1e10? And why would anyone put such a constant in a function scope rather than a more global scope, where it would be evaluated exactly once in the lifetime of the program (instead of every time the outer function is called)?

I find these questions so distracting, they totally obscure the point the article was trying to make, and the example is essentially an anti-pattern for the usage of closures.

Collapse
 
ahmedgmurtaza profile image
Ahmed Murtaza • Edited

Hi @pcockerell , I value your anguments, however keeping global values itself is an anti pattern. Second, using high exponential expressions was only meant to show jobs which could affect smooth performance. Although these patterns might not benefit in smaller code bases but would surely payoff in much larger applications.

Collapse
 
pcockerell profile image
Peter Cockerell

I didn't say a global variable, but "a more global scope", e.g. a static class property or a module-level const in node. The problem with paring your example down to such a degree is that you haven't shown closures' real utility. I think at the very least the outer function should have taken a parameter, so you could show that the inner function is implicitly parametrized by the argument passed to the outer function (similar to the currying that others have mentioned).

Another way to achieve exactly the same effect would be to make multiply() a method of a class Multiply, which has a fixed instance variable "x" per your example, or having x initialized in the constructor in the more realistic example. Though admittedly this is less in the spirit of function-based Javascript and more in line with the later class-based syntactic sugar.

In any case, I still don't believe that an example of closures where the only captured variable is identical for all instances of the closure is a very useful one.

 
ahmedgmurtaza profile image
Ahmed Murtaza

@lukeshiru I totally endorse this approach enabling multiply() to be pure function and fully reusable

Thread Thread
 
ahmedgmurtaza profile image
Ahmed Murtaza

Agreed! :)

Thread Thread
 
danielo515 profile image
Daniel Rodríguez Rivero

Yes exactly.
The author obviously has no idea of what curried functions are and why they are useful. Looks like he just discovered what closures are and wanted to write an article about it, even without knowing what they are useful for or what the term memory leak actually means. The example code proposed by @luke 知る has way more value and shows a better understanding of the topic than the article itself.

Thread Thread
 
hasnaindev profile image
Muhammad Hasnain

Let's cut him some slack, he's newbie. Instead, why don't you read one of my post and participate in the discussion? I'll be really grateful.

dev.to/hasnaindev/what-is-your-go-...

 
hasnaindev profile image
Muhammad Hasnain

I see. 🤦🏽‍♂️

Thread Thread
 
ahmedgmurtaza profile image
Ahmed Murtaza

@hasnaindev your point is valid too :) , its just 2 diff points what @lukeshiru has shared out above.

Collapse
 
matiaslopezd profile image
Matías López Díaz • Edited

What do you think in this case with socket.io?

namespace.on('connection', socket => new EventRouter(socket));
Enter fullscreen mode Exit fullscreen mode

If I'm not wrong, every time a user connect to Websocket, will be create a new instance of EventRouter. If the user disconnect, the garbage collector will destroy that instance of EventRouter.

Collapse
 
omrisama profile image
Omri Gabay

This is neat. Never thought of this as a possibility before!

Collapse
 
ahmedgmurtaza profile image
Ahmed Murtaza

Thanks @omrisama

Collapse
 
wagyourtail profile image
wagyourtail

With the given example, Im pretty sure v8 would be capable of optimizing away the "Math.pow" to a constant..

Collapse
 
supportic profile image
Supportic • Edited

Still waiting for the proof that it's more efficient. ^^ I might be wrong but I think the regular function gets garbage collected after usage unlike the scope of the closure.