Why does `let` set the value of variables in a function when it's defined?

Tell us what’s happening:

In the ES6 challenges, the sample code shows that can use let to assign values to variables in a function at the time we define the function. This is unlike var, which assigns values at the time that we run the function.

Do you know why let works this way?

I think I understand the other properties of let, where you can only assign a variable once per block/statement/expression, and where its scope is local to the block/statement/expression, but I don’t understand how that causes let to assign values to functions at the time when they’re defined.

Can you explain what causes this?

Thank you!

Your code so far

// This is a slight variation on the sample code
'use strict';
var printNumTwo;
for (let i = 0; i < 5; i++) {
  if (i === 2) {
    printNumTwo = function() {
      return i;
    };
  }
}
console.log(printNumTwo());
// returns 2
console.log(i);
// returns "i is not defined"

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36.

Link to the challenge:
https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/es6/compare-scopes-of-the-var-and-let-keywords

The only difference that let has from var is that var has the scope of the entire function it’s defined in, whereas let has the smaller scope of the enclosung block (such as an if condition, or a loop). If one uses let at the top level of a function, it’s exactly the same as var.

It’s not evaluated at the time of definition: If it were, this would result in console output without even calling foo(). You’re also perfectly able to redefine variables defined with let; it’s const that doesn’t allow for it.

function noisyVal() {
    console.log("Hey, look at me!");
    return 123;
}

function foo() {
    let x = noisyVal();
}

Thanks, Chuck. Can you help me understand why the sample code I shared will return the number 2, instead of 4?

I understand that if i were a var instead of a let then the function would return 4.

But I don’t understand why let causes the function to return a 2.

In your loop, when i is 2, you create an entirely new function that captures the value of i in its enclosing scope at the time of definition, then bind that function to the printNumTwo variable. This sort of “inner function” is called a closure, and it’s not a property of let so much as how function definition works, by “snapshotting” the local variables in the outer scope at the time the outer scope exits.

Beyond showing simple counter/adder examples, it’s hard for me to explain closures in a single post, but thankfully I can stand on the shoulders of giants and point you at a SO post that does the job far better than I could:

Your example in particular is curious, since I would have expected it to take on the binding of i in the loop, and therefore be set to 5, not 2. That actually does appear to be a particular behavior of let over var in loops, with each iteration getting a fresh binding rather than sharing the same one. Looks like I learned something from your question today :slight_smile:

Thank you, Chuck! This helps a lot!