Compare Scopes of the var and let Keywords - why does function keep returning numbers

I did the challenge no problem but it’s the stuff on the left that’s driving me crazy! Specifically the code below. I changed the middle part of the for statement from 3 to 30000 to make it clearer for me. i starts at zero and starts to climb one number per iteration. When it hits 2 the if statement fires and returns 2. Now as I see it the for statement could keep climbing to 30 billion but the number that is returned should never change from 2 because the actions within the if statement will never “know about it” because the if statement is only true once.
Really bugging me!

var printNumTwo;
for (var i = 0; i < 30000; i++) {
  if (i === 2) {
    printNumTwo = function() {
      return i;
    };
  }
}
console.log(printNumTwo());
// returns 30000

Love the question:

  1. In " i === 2" condition block, anonymous function returning “i” gets assigned to printNumTwo.
  2. the “i” returned from the function has a closure over the “i” from the loop.
for (var i = 0; i < 30000; i++) {
  if (i === 2) {
    printNumTwo = function() {
      return i; // this has closre over var i above
    };
  }
}

One solution is to add a break statement to prevent the loop from going further.

for (var i = 0; i < 30000; i++) {
  if (i === 2) {
    printNumTwo = function() {
      return i;
    };
    break;
  }
}

printNumTwo()

Another solution is to capture the value of “i” at the iteration you wish (const y = i ) and no closure over loop block value of variable “i” will be on the way.

for (var i = 0; i < 30000; i++) {
  if (i === 2) {
    const y = i; //or let or var
    
    printNumTwo = function() {
      return y;
    };
  }
}

printNumTwo()

Does it make sense?

Thanks for replying! Yes, both those solutions work great. As does the one suggested by the lesson: changing
var i = 0
to
let i = 0

But what I still don’t understand is that since the if statement is only “triggered” once AND the return statement is inside the if statement why does it continue returning numbers?? IOW at the end of the day i may be equal to 30000 but the value of i was returned to the console way back at the 3rd iteration when its value was only 2.

that’s the difference in the behaviour of a variable declared with var (which is function scoped) and a variable declared with let (which is block scoped, where a block is between {})

in the case with var the i has a global scope, where it keeps growing as the loop keep going, after the loop it is the global i with a value of 3, or 3000, that the function has access to

with let instead the variable is local to the loop, outside the loop it doesn’t exist, so when the function is called, it has access only to the i that has value of 2 existing inside the loop when the function is defined

1 Like

The return statement does not break the loop.
The return statement inside the function is executed on the function call later . The loop has already run.

printNumTwo = function() {
      return i; 
    };

I believe the confusion comes from the code above.
That part of the script just makes assignment of function as value to printNumTwo.
No return line executed so far.

return i gets executed later on function call.
printNumTwo()

OH now I get it. Thank you. The universe is back in balance.

Great question! It made me really think. I would like to add a little something, if I may, that helped me understand the code.

The JS engine will read the keyword ‘var’ on the variable name printNumTwo, and so ‘printNumTwo’ is placed in ‘global’ memory in the execution environment, or memory heap. That is the nature of the infamous var.

As we know, JS is a single-threaded language,meaning the engine reads code, ‘line-by-line’, from top-to-bottom.

When the JS engine gets to the for…loop ‘conditional statement’

 i < 3000; 

as we know, it’s true, and heads to the next line of code.

Since ’ i ’ was declared with keyword var inside the for-loop, when the JS engine has finished the conditional evaluation, it goes to the next line which is the if statement. When the JS engine parses the ’ i ’ inside the if conditional

if ( i === 2) {

it looks for i in memory. It finds it in global memory having been assigned to the ’ var ’ in the for-loop’s variable ‘i’ declaration with a value of 0. It returns back up in the for-loop, having retrieved the ‘i’ s value from global memory. The ‘var’ has done it’s work. It will just keep evaluating the for-loop conditional, then parses the i in the if statement, then back into the for-loop and, keeps incrementing until 30000< 30000 returns a falsy. This ‘breaks’ the JS engine out of the for-loop and down to the function call.

console.log(printBumTwo());
var printNumTwo;     // globally scoped

for (var i = 0;  i < 30000;  i++) {
     if (i === 2) {
          printNumTwo = function() {

          return i;
     };

  }
}

console.log(printNumTwo());

// returns 30000

When the function is invoked, the JS engine creates a new function execution context. The printNumTwo() gets placed on the call-stack. The ‘return’ statement is read by the JS engine; "return the value of i and exit the function, and, please, pop the function off the call-stack. Get to work! The engine looks for i in the context of it’s execution environment. It’s not found i in the block scope of the function, so it looks it looks up the chain, and finds it in the global memory. It returns the value of i, 30000.

You can see this in the Chrome developer tools if you debug this code.

I read all the reply’s and learned from this great question.

I changed the ‘var’ to let in the for-loop.

To really appreciate the block scope that const and let give, you have to look at how one used to create such a scope. Not too long ago, you had to use an IIFE (Immediately Invoked Function Expression) to “lock in” the value of a variable within a loop (a block) to be referenced later in a function that is called at a later time.

For example, you would have to do something like either one of the following:

var printNumTwo;
for (var i = 0; i < 30000; i++) {
  (function(y){
    if (i === 2) {
      printNumTwo = function() {
        return y;
      };
    }
  })(i);
}
printNumTwo() // 2

OR

var printNumTwo;
for (var i = 0; i < 30000; i++) {
  if (i === 2) {
    (function(y) {
      printNumTwo = function() {
        return y;
      };
    })(i);
  }
}
printNumTwo() // 2

Actually I “mostly” get it. As I internalize JS more and more it will probably sink in better. Thanks for all the replies.