DEV Community

loading...
Cover image for Why you should stop declaring variables inside a for loop (especially in JavaScript)

Why you should stop declaring variables inside a for loop (especially in JavaScript)

Nicola
I'm a FrontEnd developer, specialized on Angular platform. I love design and music!
・2 min read

In my career, I've worked a lot with different programming languages, especially with C# and javascript.

During my years of development, I've faced a lot of performance issues, on Mobile and Desktop applications.

Often, the main reasons for performances lack are scripts.
Scripts are the main part of our website/applications, they weight a lot and they use a lot of resources, like CPU and RAM.

In Javascript, there isn't a manual garbage collector system (like c# or c++ malloc or free method), so you need to optimize the resources management part of your application to prevent memory leaks, zombie data or other performance issues.

But what can we do, as developers, to decrease the RAM usage for example?

we need to write good code, reuse resources and handle data smartly.

For example, don't declare variables inside a for loop until it is really necessary!

Why

A for loop is a looped method invocation, so a variable declared inside a for loop will exists inside the loop scope only, and it will be initialized
in every loop iteration! This means than a space in memory for the variable will be allocated in each loop cycle!

What the heck!

This fact, combined with the typeless Js language could create big memory leaks, performance issues, and other bad behaviors.

A simple, but effective method to recycle RAM is to declare variables once and reuse them when needed. The for loop is the simplest case but you can replicate this solution everywhere.

Example

BAD

Declare cursor inside the for loop and a string inside too.

for (let i = 0; i < 1000; i++) {
  let logString = 'Current is: ' + i;
  console.log(logString);
}
console.log('%o', window.performance.memory);
{totalJSHeapSize: 68876822, usedJSHeapSize: 46264582, jsHeapSizeLimit: 2197815296}

GOOD

Declare cursor and logString outside the for loop:

let logString = '';
let i = 0;
for (i = 0; i < 1000; i++) {
  logString = 'Current is: ' + i;
  console.log(logString);
}
console.log('%o', window.performance.memory);
{totalJSHeapSize: 57747133, usedJSHeapSize: 45757109, jsHeapSizeLimit: 2197815296}

As you can see the totalJSHeapSize in the second implementation is less than the first one (< 11129689‬byte ~ 11mb), the usedJSHeapSize too (< 507473byte‬ ~ 0.5mb).

I know, this isn't a great saving, but with large projects, this could save your web application performances!

Conclusion

There are a lot of optimization we can use to boost our applications, and this solution is not only JS-related, you could use it with any language you use.
But I think JS is the best case to use, because it's not resource-optimized.

I hope you found this article useful or just interesting!

Discussion (46)

Collapse
curtisfenner profile image
Curtis Fenner • Edited

I really doubt this is right at all, and this is not a good experiment to show that it is right.

1) You're not recording the heap before hand, so it's impossible to know how much has actually changed

2) You're not running multiple experiments. The garbage collector is not deterministic, so you'll get slightly different numbers each time. At the least, you should be reporting an average over many trials.

3) You're logging to the console in this experiment. Logging to the console expends huge amounts of memory and CPU which are going to make the results far too variable to be useful.

For the record, when I tried measuring the difference, I saw no difference; the amount of memory that had been used by any single run was basically random, and neither was more or less than the other.

Remember the three rules of optimization:

1) Don't.

2) Don't, yet. (Experts only)

3) Profile before optimizing. Even if this experiment showed that variables inside loops use more memory, that wouldn't justify the blanket advice to not declare variables in loops, because that's probably not where most of your program is using its memory; instead of hurting readability and violating best-practices, you should find where your program is actually wasting most of its memory.

Collapse
danielrogowski profile image
danielrogowski

Seconding your answer.

Collapse
nicolalc profile image
Nicola Author

Yes you're right, I'll keep your tips for the next time!

Collapse
peq profile image
Peter Zeller

Don't wait until next time. This is harmful, bad advice. You should add a big fat disclaimer to the article that so you made mistakes here.

Collapse
technicaljohn profile image
Technical John • Edited

Please look further down the comments for a comment by Jan Hasebos. It would address your perception regarding the iterated declaration, while also respecting the variable scope of the for loop.

Really, you'd be better off saying the "bad" example is ok, while you feel the "good" example is better.

I added some thoughts below Jan to expand on this.

Collapse
lesha profile image
lesha 🟨⬛️

I want to buy you a beer

Collapse
ahferroin7 profile image
Austin S. Hemmelgarn

For loop-internal variables, this shouldn't actually save any memory on average. I mean, if the garbage collector is really stupid (most aren't), it might, but not much unless you've got a lot of variables inside the loop that are only ever primitives. OTOH, it should save some time, because you don't have to allocate memory on every loop iteration.

For the actual counter variable though, it's not as clear cut. In terms of runtime and memory usage, there should be near zero difference in the totals between these two code samples:

let i
for (i = 0; i < 10; i++) {
    // do something
}
for (let i = 0; i < 10; i++) {
    // do something
}

The reason is simple: Internally, that first part of the loop expression (the i = 0 part) runs exactly once. So moving the counter declaration outside of the loop expressions just changes the scope, nothing else. Somewhat counter-intuitively, this may even make the loop run slower if you are accessing the counter inside the loop a lot (I know of no JS engine that actually has this issue, but a trivial naive implementation of the JS scoping rules would cause it to run slower).

If, however, you can safely reuse the counter variable across multiple loops, then you might save some time this way, but you won't really save much space long-term. The important part there though is 'safely', you usually have to at least scope the declarations to the containing function so that you don't end up manipulating other loops states accidentally, so you're not likely to save much time either unless you're doing a lot of looping.

Collapse
jamesthomson profile image
James Thomson

Internally, that first part of the loop expression (the i = 0 part) runs exactly once. So moving the counter declaration outside of the loop expressions just changes the scope, nothing else.

Came here to say this. Placing the let within the for expression doesn't mean it's being created every loop. It's only created once, it's more concise, and it's scoped to the for loops block. Unless you have good reason to (e.g. keeping track of a counter), to place it outside is just bad practice.

Collapse
warrior97 profile image
warrior97

Also, don't know about javascript, some compilers optimise your code so that variable declarations inside a loop when turn to machine code are placed outside the 'loop'.
You can never know how a compiler will ootimise your code. Interpreters are a whole different story. Correct me if I'm wrong. There are other things you can do to optimise your code and better manage memory. First thing is algorithm complexity and storing excess data or use blocks of code so you limit the life of a variable and etc.

Thread Thread
jamesthomson profile image
James Thomson

There's definitely more important optimisations than worrying about a for loop. Unless you are looping through tens of thousands of objects - bit that's a whole different story.

Thread Thread
ahferroin7 profile image
Austin S. Hemmelgarn

If it were almost anything but memory allocation, I would agree wholeheartedly. Allocating memory, however, is literally one of the slowest runtime operations possible in pretty much any language you care to name (coincidentally, this is most of why C++'s std::string is so slow compared to C-style strings, pretty much any call that 'mutates' a string involves allocating memory for a new one).

The exact benefit may not be huge in terms of time, but it's a good habit to just write optimal code in cases like this where it's easy.

Thread Thread
ahferroin7 profile image
Austin S. Hemmelgarn

There are indeed other things you can do to optimize your code, but eliminating memory allocations (and de-allocations) is one of the easier ones that's consistently a win in most languages. That's one of the other reasons to group your variable declarations at the top of a function aside from making the code easier to read, some runtime environments and compilers will actually be able to further optimize things so that there are fewer allocations that need to be made that way.

Collapse
leviem1 profile image
Levi Muniz

Look, this article needs to be taken down. It's very harmful to novice programmers and more experienced programmers will probably just leave a comment saying "you're wrong". That being said, I feel bad for the author. He likely has no idea that this is bad advice and this is all probably discouraging. My advice for the author: please take the article down or revise it so that beginners don't draw incorrect conclusions. Afterwards, write your next article! I'd love to see you write about frontend or something else you're interested in!

Collapse
karataev profile image
Eugene Karataev

In Javascript, there isn't a Garbage collector system, so you need to handle yourself the resources management part of your application, and this could be a big mess.

Why? JavaScript has automatic garbage collection mechanism.

Collapse
nicolalc profile image
Nicola Author

Yes sorry, I mean than "you cannot manually deallocate resources" like c# or c++ malloc or free

Collapse
jamesthomson profile image
James Thomson

Sure you can, if you null an Object then it will be marked for GC. It may not happen immediately, but when the GC runs it will be cleaned up.

An object is said to be "garbage", or collectible if there are zero references pointing to it.

developer.mozilla.org/en-US/docs/W...

Collapse
dentych profile image
Dennis

C# has garbage collection and no way to manually free resources. It works the same as javascript, you can set all references to an object to null and then it will eventually be garbage collected. You can also force the garbage collector to run, but that is highly discouraged in almost all circumstances.

Collapse
adam_cyclones profile image
Adam Crockett

Unfortunately we just have to trust that is what is happening but it's okay, we don't want to panic anyone. Can you see what I did there.

Collapse
aamonster profile image
aamonster

Just don't write in JavaScript if you need fast and memory efficient code.

This thing is premature optimization. You should prefer code clarity over efficienty until you absolutely need last one (about 1% of your code I suppose).

Collapse
chriskarpyszyn profile image
Chris Karpyszyn

Declaring variables outside of your loop to use inside of a loop could cause unintended consequences if the code in the loop is complicated. Reusing variables might seem efficient until you need to fix a bug introduced by another developer.

Unless performance is really essential and in this case it's probably negligible, writing cleaner code is more important imo.

Collapse
corruptscanline profile image
corruptscanline

Pretty much everything here is wrong.

The iterator should be stack allocated, and is only initialized once anyway.

The logString variable is assigned to a new value each iteration and goes out of scope at the end, or at least gets reassigned. There aren't 1000 variables with heap string references waiting to be deallocated when the loop ends.

Collapse
rumkin profile image
Paul Rumkin

There is an excellent explanation by v8 developers of how memory allocation works in v8 JS engine.

Collapse
adam_cyclones profile image
Adam Crockett

I would read this before anyone jumps to conclusions.

Collapse
somedood profile image
Basti Ortiz • Edited

Whoa, this is crazy. I have always dismissed the declaration inside the loop having a very minimal performance impact. Apparently my assumptions were wrong.

Thanks for this! Will definitely take note of this from here on out.

Collapse
jwkicklighter profile image
Jordan Kicklighter

Whao, this is crazy

That's because it is incorrect. Please read the rest of the comments in this article for better explanations.

Collapse
somedood profile image
Basti Ortiz

Thanks for the heads up!

Collapse
nicolalc profile image
Nicola Author

Thanks a lot! Check this video from Unity, is for C# but some principles could be applied to general programming languages, it shows a lot of data optimization and performance boost using the Data-Driven programming pattern

Collapse
somedood profile image
Basti Ortiz

Thanks! I definitely will watch it.

Collapse
hookumsnivy profile image
hookumsnivy

Please don't do this and take this article down. As others have mentioned, the experiment is flawed.
Don't prematurely optimize. It can lead to a number of problems:

  1. It becomes harder to read and understand
  2. It can lead to future errors
  3. Go after the big fish first. Find the performance bottlenecks first and fix those before worrying about the tiny things.

Don't complicate your code unless absolutely necessary. Follow the KISS principal.

I recently found myself reviewing code from a senior software engineer that was filled with premature optimizations. It was very hard to read and created an abundance of code for what purpose? Just to save a negligible amount of time - we're talking far less than a millisecond. Orders of magnitude less than your standard network delay.

Don't be that engineer.

Don't encourage others to be that engineer.

Collapse
jmccabe profile image
John McCabe

The figures you quote there seem excessive. To be honest, I don't know javascript but every half-decent language I've ever used would consider logstring as going out of scope at the end of each loop iteration in the first version, so would free/deallocate any memory that was acquired at its declaration.

Also, what's the effect if you do:

let logString = 'Current is: ';
let i = 0;
for (i = 0; i < 1000; i++) {
console.log(logstring + i);
}
console.log('%o', window.performance.memory);

Collapse
belverus profile image
Belverus

I couldn't understand this. Why does not garbage collection handle unused memory space? It usually clears when access to a specific location is not possible. I also never saw something like this in any complexity analysis.

Collapse
guneyozsan profile image
Guney Ozsan

Dr. Strangeoptimization or: How I Learned to Stop Worrying and Love the Compiler

Collapse
jwkicklighter profile image
Jordan Kicklighter

Like several people have said, this article needs to be updated with a disclaimer that it isn't accurate and the tests are far from comprehensive. Folks bookmarking this article are being mislead, and it's unfortunate for newer developers.

Collapse
sinjai profile image
Sinjai

Since you mentioned C#, it may be worth noting that you can define a C# variable within a loop without changing the resulting IL (the code that's actually run by the VM) at all. This is very easy to test.

However, there are instances, such as when using that variable within a lambda, in which this isn't true. That said, more often than not you actually should declare your variables inside the loop in C# so they are scoped properly.

Collapse
idimiter profile image
Dimitar Dimitrov

JavaScript hoists all function variables on top of the function. You can even define a variable at the end and it will still work. Also there is no block scope variables in JavaScript. Sorry but this article is wrong.

Collapse
brooknovak profile image
brooknovak

Although it's really great to point out and discuss low level performance concerns for things like loops...

The majority of the time it's better to have maintainable and readable code over optimized code. Compilers and garbage collectors usually do a good job most of the time!

IMO creating variables outside a loop for the sake of optimization adds code smells for little to no benefit!

Collapse
adam_cyclones profile image
Adam Crockett

Garbage collected 😉

Collapse
technicaljohn profile image
Technical John

Thank you! This is exactly what I was looking to add.

I see what the OP is attempting to draw attention to: instantiating the variable every iteration seems wasteful... Of something. He drew attention to the one thing that seemed to stand out, memory. But in truth it's not that simple, as the comments have shown.

The better answer, if loop declaration us bothering you, is to put the variable declaration in a spot that it only happens once. Either in the declarative part of the if, like in the above comment, or the more verbose way of using an if to declare the variable on first run.

I suppose my question would be, how much garbage does it create if the "bad" loop were to run 1 million times? What about in a complex situation where the GC is somehow occupied, and this loop runs 1 million times?

For sure, more testing and fuzzing is needed before coming to a conclusion regarding the "bad" way.

Collapse
jtiscione profile image
Jason Tiscione

You're implicitly assuming that JS is allocating stack variables similar to how a compiled language like C# does it. The interpreter in JS is running at a much higher level and if a variable is declared inside a loop it will effectively run the code as if the variable were declared outside. It won't do a reallocation like you would see in .NET bytecode.

Collapse
vjnvisakh profile image
Visakh Vijayan

Nice one ...have the habit of doing this. Shall change this from now onwards.

Collapse
technicaljohn profile image
Technical John • Edited

Please read the other comments before you change your habit... There is more to this story, and the "good" snippet in the article is flawed.

Collapse
jwkicklighter profile image
Jordan Kicklighter

There's no need to do that. Please read the rest of the comments here, as the article is not really correct.

Collapse
charlyjazz profile image
Carlos Azuaje

Wtf dude delete it

Collapse
thebeneficent profile image
TheBeneficent

There’s a ‘static’ declerator in C, so I guess it’s not in the JS, right?

Collapse
maixuanhan profile image
Han Mai

Sorry. No way! Please research JS GC again.
When you assign global variable logString to the new value, you implicitly leave the old value as 'garbage' waiting for being collected. Same thing happen when you use the variable as a local one, after each iteration, the stack variable is revoked, leave the immutable string object as garbage waiting for being collected. Even if you use global, you leak the last string, leave it unused and still have reference to it hence the GC cannot detects it.

Furthermore, your measurement method seemed to be wrong, you even didn't isolate the scenario. No way your simple script took that much memory. Your browser must have opened other pages that eating resource (like fb, messenger...) at the same time that interfered your measurement result.

I've tried your both 2 snippets, no different, even a byte.