127

Regardless of functional differences, does using the new keywords 'let' and 'const' have any generalized or specific impact on performance relative to 'var'?

After running the program:

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

.. My results were the following:

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

However discussion as noted here seems to indicate a real potential for performance differences under certain scenarios: https://esdiscuss.org/topic/performance-concern-with-let-const

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
sean2078
  • 5,131
  • 6
  • 32
  • 32
  • I think that depends on the usage, for instance `let` used in block scope should be more performant than `var`, which doesn't have block scope, but only function scope. – adeneo Oct 16 '16 at 13:13
  • 1
    If I may ask, why is that @adeneo? – sean2078 Oct 16 '16 at 13:14
  • 1
    You are over optimizing. Using `let` and `const` are a benefit to the developer, helping them understand the code and making it more reliable. Developer time costs a lot and is worth the expense of time until it actually becomes a problem. – synthet1c Oct 16 '16 at 13:14
  • 1
    @sean2078 - if you need to declare a variable that only lives in a block scope, `let` would do that, and then be garbage collected, while `var`, which is function scoped, wouldn't neccessarely work the same way. Again I think, it's so specific to the usage, that both `let` and `const` *can* be more performant, but wouldn't always be. – adeneo Oct 16 '16 at 13:17
  • 3
    I'm confused by how the quoted code is meant to demonstrate any difference between `var` and `let`: It never uses `let` at all. – T.J. Crowder Oct 16 '16 at 13:27
  • 1
    Currently is does not - only const vs. var .. Originally sourced from https://gist.github.com/srikumarks/1431640 ( credit to srikumarks ) however request was made to pull code into question – sean2078 Oct 16 '16 at 14:32

6 Answers6

174

TL;DR

In theory, an unoptimized version of this loop:

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

might be slower than an unoptimized version of the same loop with var:

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

because a different i variable is created for each loop iteration with let, whereas there's only one i with var.

Arguing against that is the fact the var is hoisted so it's declared outside the loop whereas the let is only declared within the loop, which may offer an optimization advantage.

In practice, here in 2018, modern JavaScript engines do enough introspection of the loop to know when it can optimize that difference away. (Even before then, odds are your loop was doing enough work that the additional let-related overhead was washed out anyway. But now you don't even have to worry about it.)

Beware synthetic benchmarks as they are extremely easy to get wrong, and trigger JavaScript engine optimizers in ways that real code doesn't (both good and bad ways). However, if you want a synthetic benchmark, here's one:

const now = typeof performance === "object" && performance.now
    ? performance.now.bind(performance)
    : Date.now.bind(Date);

const btn = document.getElementById("btn");
btn.addEventListener("click", function() {
    btn.disabled = true;
    runTest();
});

const maxTests = 100;
const loopLimit = 50000000;
const expectedX = 1249999975000000;

function runTest(index = 1, results = {usingVar: 0, usingLet: 0}) {
    console.log(`Running Test #${index} of ${maxTests}`);
    setTimeout(() => {
        const varTime = usingVar();
        const letTime = usingLet();
        results.usingVar += varTime;
        results.usingLet += letTime;
        console.log(`Test ${index}: var = ${varTime}ms, let = ${letTime}ms`);
        ++index;
        if (index <= maxTests) {
            setTimeout(() => runTest(index, results), 0);
        } else {
            console.log(`Average time with var: ${(results.usingVar / maxTests).toFixed(2)}ms`);
            console.log(`Average time with let: ${(results.usingLet / maxTests).toFixed(2)}ms`);
            btn.disabled = false;
        }
    }, 0);
}

function usingVar() {
    const start = now();
    let x = 0;
    for (var i = 0; i < loopLimit; i++) {
        x += i;
    }
    if (x !== expectedX) {
        throw new Error("Error in test");
    }
    return now() - start;
}

function usingLet() {
    const start = now();
    let x = 0;
    for (let i = 0; i < loopLimit; i++) {
        x += i;
    }
    if (x !== expectedX) {
        throw new Error("Error in test");
    }
    return now() - start;
}
<input id="btn" type="button" value="Start">

It says that there's no significant difference in that synthetic test on either V8/Chrome or SpiderMonkey/Firefox. (Repeated tests in both browsers have one winning, or the other winning, and in both cases within a margin of error.) But again, it's a synthetic benchmark, not your code. Worry about the performance of your code when and if your code has a performance problem.

As a style matter, I prefer let for the scoping benefit and the closure-in-loops benefit if I use the loop variable in a closure.

Details

The important difference between var and let in a for loop is that a different i is created for each iteration; it addresses the classic "closures in loop" problem:

function usingVar() {
  for (var i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("var's i: " + i);
    }, 0);
  }
}
function usingLet() {
  for (let i = 0; i < 3; ++i) {
    setTimeout(function() {
      console.log("let's i: " + i);
    }, 0);
  }
}
usingVar();
setTimeout(usingLet, 20);

Creating the new EnvironmentRecord for each loop body (spec link) is work, and work takes time, which is why in theory the let version is slower than the var version.

But the difference only matters if you create a function (closure) within the loop that uses i, as I did in that runnable snippet example above. Otherwise, the distinction can't be observed and can be optimized away.

Here in 2018, it looks like V8 (and SpiderMonkey in Firefox) is doing sufficient introspection that there's no performance cost in a loop that doesn't make use of let's variable-per-iteration semantics. See this test.


In some cases, const may well provide an opportunity for optimization that var wouldn't, especially for global variables.

The problem with a global variable is that it's, well, global; any code anywhere could access it. So if you declare a variable with var that you never intend to change (and never do change in your code), the engine can't assume it's never going to change as the result of code loaded later or similar.

With const, though, you're explicitly telling the engine that the value cannot change¹. So it's free to do any optimization it wants, including emitting a literal instead of a variable reference to code using it, knowing that the values cannot be changed.

¹ Remember that with objects, the value is a reference to the object, not the object itself. So with const o = {}, you could change the state of the object (o.answer = 42), but you can't make o point to a new object (because that would require changing the object reference it contains).


When using let or const in other var-like situations, they're not likely to have different performance. This function should have exactly the same performance whether you use var or let, for instance:

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}

It's all, of course, unlikely to matter and something to worry about only if and when there's a real problem to solve.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for the answer - I agree, so for myself have standardized on using var for looping operations as noted in your 1st for loop example, and let / const for all other declarations assuming that the performance difference is essentially nonexistent as the performance test would seem to indicate for now. Perhaps later, optimizations on const will be added. That is, unless someone else can show a discernible difference via code example. – sean2078 Oct 22 '16 at 23:16
  • 1
    @sean2078: I use `let` in the loop example as well. The performance difference just isn't worth worrying about it in the 99.999% case. – T.J. Crowder Oct 23 '16 at 08:24
  • @T.J.Crowder I'm pretty sure that in your loop example both var and let would be JIT compiled to the exactly the same machine code. – Dan M. May 01 '17 at 22:57
  • @DanM.: You'd be mistaken, currently: https://jsperf.com/let-vs-var-in-loop At some point engines may do enough introspection of the loop code to know they can optimize-away the fact that the `let` version has multiple `i` variables it has to copy the value between at the end of each loop iteration, but they don't currently seem to. – T.J. Crowder May 02 '17 at 06:30
  • @T.J.Crowder well, that's surprising. Never expected something like this from V8. It works with expected (i.e. same) perf in IE/Edge and in Firefox. Seems to be a "bug" in Chrome/Node.js/V8. – Dan M. May 03 '17 at 14:00
  • @DanM.: I get a full 2% difference in the latest Firefox. Can't compare performance with IE11, IE11 implements `let` incorrectly. :-) But sure, presumably at some stage, if it ends up being worthwhile, more aggressive optimization will be done. – T.J. Crowder May 03 '17 at 14:03
  • @T.J.Crowder 2% is a margin of error) But I did some more research and it all seems to be related to `Unsupported let compound assignment` in V8 optimizer http://stackoverflow.com/questions/34595356/what-does-compound-let-const-assignment-mean It's a shame, that there is a still such major oversight, since the problem seems to be at least 1 year old. – Dan M. May 03 '17 at 14:15
  • @DanM.: That compound assignment thing is interesting, but I don't think it's the cause here, as the "outer `let`" test is just as fast as the `var` test and `i = i + 1` (non-compound assignment) when `i` is scoped to the loop is just as slow. I think, again, it's because V8 isn't (yet) optimizing away the multiple `i` variables when `i` is scoped to the loop. (This is, btw, what IE11 gets wrong -- and fair enough, they did their `let` before the spec was finalized.) – T.J. Crowder May 03 '17 at 14:29
  • @T.J.Crowder found relevant bug: https://bugs.chromium.org/p/v8/issues/detail?id=4762 Btw, I wanted to link this SO answer which hinted to why it was unoptimized http://stackoverflow.com/a/40460982/4114447 – Dan M. May 03 '17 at 15:05
  • @DanM.: Yeah, that's the kind of thing I was expecting to be the cause. Cool to see a commit for it. April 19th, it'll make its way through the chain soon enough. – T.J. Crowder May 03 '17 at 15:10
  • 3
    As of Mid-2018, the versions with let and var have the same speed in Chrome, so now there is no difference anymore. – Max May 10 '18 at 23:37
  • @Max: And on Firefox. Excellent. (I can't get consistent results with Edge, interestingly, even if I [tweak it so each loop takes longer](https://jsperf.com/let-vs-var-in-loop-again).) You knew that had to be an optimization priority, glad to see it's happened. – T.J. Crowder May 11 '18 at 07:12
  • 1
    @DanM.: Good news, optimization appears to have caught up, at least in V8 and SpiderMonkey. :-) – T.J. Crowder May 11 '18 at 07:26
  • @T.J.Crowder nice. Btw, one nitpick regarding your definition of `const`. It means that the variable can't be reassigned (like Java's `final`) not that the referenced value cannot change (like `const` from C++). I.e. it's perfectly fine to assign some array to `const foo` and the push a few elements into it. – Dan M. May 11 '18 at 11:41
  • @DanM. - Modifying the object the value (an object reference) refers to isn't in any way modifying the value in the `const`. – T.J. Crowder May 11 '18 at 11:54
  • @T.J.Crowder I understand what you mean. My point is I doubt many people would. I.e. I think when asked what is the value of `foo` in `const foo = [42]`, the typical answer would be an array containing `42`, not a "reference to an array". I've seen a great deal of confusion on this matter in a "does Java pass by reference or by value" SO question and I'd expect that js world is not clearer in that regard. – Dan M. May 11 '18 at 12:03
  • @DanM. - We can't find the reference misunderstanding battle everywhere. :-) I've added footnote. – T.J. Crowder May 11 '18 at 12:08
  • @DanM. - Oh, I know that post well. :-) – T.J. Crowder May 11 '18 at 12:11
  • Awesome answer pointing out the difference in scope, especially your example. I had even considered that. – Andrew Steitz Apr 03 '19 at 14:58
  • I believe this is wrong answer. let is not recreated each time but allocated each time. in loop its behave same way as var. You would not see differences on small memory footprint. you can't change allocated size of const which doesn't make const faster but ecosystem more stable. I think you should look at bigger picture not how const and let perform with single loop. – Eduard Jacko May 06 '20 at 09:20
  • @EduardJacko - The answer is correct. The spec is [quite explicit about it](https://tc39.es/ecma262/#sec-forbodyevaluation), see the step 3.e where it's [creating an environment for each iteration](https://tc39.es/ecma262/#sec-createperiterationenvironment) and copying the values of the previous iteration's bindings into it. And you can see that it works as the spec says in the Stack Snippet. – T.J. Crowder May 06 '20 at 09:23
  • [jsPerf test link](https://jsperf.com/let-vs-var-in-loop-again) is broken. Could you fix it please? – fdermishin May 31 '20 at 07:02
  • @user13044086 - *sigh*, that's at least the second if not the third time jsPerf has lost tests. – T.J. Crowder May 31 '20 at 08:19
  • I upvoted too. But let/var concern for loops is really misleading. Have a look @Amn's answer with some tests (more in comments). It's actually vice versa than theory mentioned here. *Let* is much faster for loops. So it's not "don't worry", but "feel free to choose let as it's better for performance". – hypers Oct 13 '20 at 11:27
  • @hypers - Thanks. :-) Synthetic benchmarks, particularly ones testing loops with empty bodies, are essentially meaningless. I've updated the answer to address that a bit, and to add a synthetic benchmark that actually does something in the loop body. Still synthetic, and so still highly suspect, but it doesn't show any *performance* advantage to `let` (or `var`, anymore -- in the early days back in 2016, `var` won easily, but not by enough to care about). Just the maintainability one. :-) – T.J. Crowder Oct 13 '20 at 12:04
  • 1
    Thanks. Fair enough. – hypers Oct 13 '20 at 14:08
29

"LET" IS BETTER IN LOOP DECLARATIONS

With a simple test (5 times) in navigator like that:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

The mean time to execute is more than 2.5ms

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

The mean time to execute is more than 1.5ms

I found that loop time with let is better.

Ankur Loriya
  • 3,276
  • 8
  • 31
  • 58
Amn
  • 531
  • 6
  • 9
  • 10
    Running this in Firefox 65.0, I got mean speeds of `var=138.8ms` and `let=4ms`. That is not a typo, `let` is more than 30x times faster right now – Katajun Feb 07 '19 at 21:56
  • 10
    I just trialed this in Node v12.5. I found the mean speeds are `var=2.6ms` and `let=1.0ms`. So let in Node is a bit more than twice as fast. – Kane Hooper Jul 21 '19 at 00:37
  • 8
    Just to make the usual point that performance testing is hard in the presence of optimisers: I think the let loop is being optimised away entirely - let only exists inside the block and the loop has no side effect and V8 is smart enough to know it can just remove the block, then the loop. the var declaration is hoisted so it can't know that. Your loops as they are I get 1ms/0.4ms, however if for both I have a variable j (var or let) outside the loop which is also incremented, I then get 1ms/1.5ms. i.e. var loop no change, let loop now taking longer. – Euan Smith Sep 03 '20 at 16:03
  • 1
    @KaneHooper - If you got a fivefold difference in Firefox, it'll have to have been the empty loop body that did it. Real loops don't have empty bodies. – T.J. Crowder Oct 13 '20 at 11:44
  • 9
    **Beware synthetic benchmarks**, and in particular ones with loops with empty bodies. If you actually do something in the loop, [this synthetic benchmark](https://jsben.ch/IWC6v) (which, again, beware of! :-) ) suggests there's no significant difference. I've also added one to my answer so it's on-site (not like those jsPerf tests that kept disappearing on me. :-) ). Repeated runs show one winning, or the other winning. Certainly nothing conclusive. – T.J. Crowder Oct 13 '20 at 12:02
  • 1
    Yeah global vars. I get a noticeable difference when I run these two loops in the console. Only for it to disappear as soon as I put the two loops in an IIFE. Firefox even seems to optimize the entire loops away inside the function. – Thomas Dec 26 '21 at 22:59
11

T.J. Crowder's answer is so excellent.

Here is an addition of: "When would I get the most bang for my buck on editing existing var declarations to const ?"

I've found that the most performance boost had to do with "exported" functions.

So if file A, B, R, and Z are calling on a "utility" function in file U that is commonly used through your app, then switching that utility function over to "const" and the parent file reference to a const can eak out some improved performance. It seemed for me that it wasn't measurably faster, but the overall memory consumption was reduced by about 1-3% for my grossly monolithic Frankenstein-ed app. Which if you're spending bags of cash on the cloud or your baremetal server, could be a good reason to spend 30 minutes to comb through and update some of those var declarations to const.

I realize that if you read into how const, var, and let work under the covers you probably already concluded the above... but in case you "glanced" over it :D.

From what I remember of the benchmarking on node v8.12.0 when I was making the update, my app went from idle consumption of ~240MB RAM to ~233MB RAM.

isaacdre
  • 842
  • 7
  • 10
3

T.J. Crowder's answer is very good but :

  1. 'let' is made to make code more readable, not more powerful
  2. by theory let will be slower than var
  3. by practice the compiler can not solve completely (static analysis) an uncompleted program so sometime it will miss the optimization
  4. in any-case using 'let' will require more CPU for introspection, the bench must be started when google v8 starts to parse
  5. if introspection fails 'let' will push hard on the V8 garbage collector, it will require more iteration to free/reuse. it will also consume more RAM. the bench must take these points into account
  6. Google Closure will transform let in var...

The effect of the performance gape between var and let can be seen in real-life complete program and not on a single basic loop.

Anyway, to use let where you don't have to, makes your code less readable.

  • 1
    Out of interest- by _what_ theory is `let` slower than `var`? Especially given the consensus in the comments on [the above answer](https://stackoverflow.com/a/54235130/1874170) showing it's faster? – JamesTheAwesomeDude Nov 28 '20 at 17:27
1

Just did some more tests, Initially I concluded that there is a substantial difference in favor of var. My results initially showed that between Const / Let / Var there was a ratio from 4 / 4 / 1 to 3 / 3 / 1 in execution time.

After Edit in 29/01/2022 (according to jmrk's remark to remove global variables in let and const tests) now results seem similar 1 / 1 / 1. I give the code used below. Just let me mention that I started from the code of AMN and did lots of tweaking, and editing.

I did the tests both in w3schools_tryit editor and in Google_scripts

My Notes:

  • In GoogleScripts there seems that the 1st test ALWAYS takes longer, no-matter which one, especially for reps<5.000.000 and before separating them in individual functions
  • For Reps < 5.000.000 JS engine optimizations are all that matters, results go up and down without safe conclusions
  • GoogleScripts constantly does ~1.5x time longer, I think it is expected
  • There was a BIG difference when all tests where separated in individual functions, execution speed was at-least doubled and 1st test's delay almost vanished!

Please don't judge the code, I did try but don't pretend to be any expert in JS. I would be delighted to see your tests and opinions.

function mytests(){
var start = 0;
var tm1=" Const: ", tm2=" Let: ", tm3=" Var: ";
    
start = Date.now();
tstLet();
tm2 += Date.now() - start;

start = Date.now();
tstVar();
tm3 += Date.now() - start;

start = Date.now();
tstConst();
tm1 += (Date.now() - start);

var result = "TIMERS:" + tm1 + tm2 + tm3;
console.log(result);
return result;
}

// with VAR
function tstVar(){
var lmtUp = 50000000;
var i=0;
var item = 2;
var sum = 0;

for(i = 0; i < lmtUp; i++){sum += item;}
item = sum / 1000;
}

// with LET
function tstLet(){
let lmtUp = 50000000;
let j=0;
let item = 2;
let sum=0;

for( j = 0; j < lmtUp; j++){sum += item;}
item = sum/1000;
}

// with CONST
function tstConst(){
const lmtUp = 50000000;
var k=0;
const item = 2;
var sum=0;

for( k = 0; k < lmtUp; k++){sum += item;}
k = sum / 1000;
}
Apostolos55
  • 574
  • 3
  • 8
  • 2
    **These results are bogus.** In the `let` and `const` cases, you're assigning to a global property `sum`, whereas the `var` case creates a local variable `var sum`. Assigning to locals is faster than assigning to globals. Fix that, and all three cases will have the same performance. (Also, the whole `psedoLoop` stuff is totally unnecessary, as the functions create blocks anyway.) – jmrk Jan 28 '22 at 12:43
-2

code with 'let' will be more optimized than 'var' as variables declared with var do not get cleared when the scope expires but variables declared with let does. so var uses more space as it makes different versions when used in a loop.