How does JS compile for loops?

I’m reading up on closures and scopes and I’ve got like 50 tabs and two pdf’s open right now, and it feels like I’m getting nowhere. :frowning:

Alright, this bit of code will not work as intended - I don’t entirely get why:

var result = [];
 
for (var i = 0; i < 5; i++) {
  result[i] = function () {
    console.log(i);
  };
}

result[0](); // 5, expected 0
result[1](); // 5, expected 1
result[2](); // 5, expected 2
result[3](); // 5, expected 3
result[4](); // 5, expected 4

Now I get that there’s compilation phase and execution phase. I get that result will first be declared, as will ‘i’ in the global scope. What I don’t get is, how does the loop itself run? How do these two phases apply for each loop iteration?

I understand that assignment is part of the execution phase, but function expressions are not declared beforehand - so… sigh, I don’t even know how exactly to ask this question.

Could someone maybe breakdown, even in pseudo code, how exactly a for loop runs, in terms of the compilation/execution phases?

The tripping part isn’t in the loop, but in the var keyword. The i variable is not block-scoped, and is well alive with its last value (5) after the loop has finished running.

If you want a block-scoped variable, use let.

Thanks for replying! I get that - the let keyword would solve this, but what I really want to know is how JS handles a for loop (in the compile/execute phases).

Your current code creates an array with 5 elements. Those elements all have the exact same function:

function () {
    console.log(i);
  };

After the for loop ends, i is 5, so when make the call to each array element, it is the same as if you would have had a named function which you call that has console.log(i);

Your above code is the same as doing the following:

function consoleFunction() {
  console.log(i);
}
var result = [];
for (var i = 0; i < 5; i++) {
  result[i] = consoleFunction;
}

To get what you want, you need to use closure with an IIFE, so that i retains it scope at the time the function is created. See below for how that is done.

var result = [];
 for (var i = 0; i < 5; i++) {
  (function(i) {
  result[i] = function() {
    console.log(i);
  };
  })(i);
}

result[0](); // 0
result[1](); // 1
result[2](); // 2
result[3](); // 3
result[4](); // 4
1 Like

JavaScript isn’t a compiled language, it’s an interpreted language. It runs everything step by step.

The for loop works exactly how you’d think it would. It changes the value of i step by step. However, when you create the function inside the for loop, i isn’t interpreted yet because the function isn’t executed yet. You’re merely creating a variable that’s equal to the function exactly as you write it. If you were to do this:

var result = [];
 
for (var i = 0; i < 5; i++) {
  result[i] = (function () {
    console.log(i);
  })();
}

It would log everything correctly.

This allows functions to be assigned to variables because they aren’t executed and interpreted until they need to be. While it may seem counter-intuitive, it allows for functional programming.

You could also curry

var result = [];
for (var i = 0; i < 5; i++) {
 result[i] = (function(number){
 	return function(){
   	 console.log(number)
   }
 })(i)
}

result[0](); // 0
result[1](); // 1
result[2](); // 2
result[3](); // 3
result[4](); // 4
1 Like

This is not really true, at least not anymore. In the old days, the browser took some JavaScript and stepped through it to run the instructions. Nowadays, the code is compiled and optimized before it is executed. The V8 engine even compiles directly to machine code. JavaScript is a specification and can be implemented many ways, but stupidly interpreting the code is just not good enough for any platform that it runs on. Regardless, this has nothing to do with the variable scope issue that @firefiber is seeing.

var result = [];
for (var i = 0; i &lt; 5; i++) {
 result[i] = (function(number){
 	return function(){
   	 console.log(number)
   }
 })(i)
}

I don’t want to seem like I’m picking on you, but this isn’t currying. You’re just using an IIFE to create a new lexical scope for the loop pointer (closure).

2 Likes

No worries, I guess my answer was outdated. It seemed to make sense in my head. I learned something new, then.

I suppose currying isn’t the right term, but could you help me understand the difference? I defaulted to using the term ‘curry’ because it looked very similar in structure. The way I understand it currying works by having partial application. Does the use of an IIFE immediately make it not curried? Supposing I allowed the second function to take in an argument.

for (var i = 0; i < 5; i++) {
 result[i] = (function(number){
 	return function(string){
   	 console.log(string + number)
   }
 })(i)
}

result[0]("Number is ");
result[1]("Number is ");
result[2]("Number is ");
result[3]("Number is ");

That kinda looks like currying. For it to be considered currying would it have to be able to have the two parameters changed at any time?

Say:

for (var i = 0; i < 5; i++) {
 result[i] = (function(number){ 
 	return function(string){
   	 console.log(string + number)
   }
 })
}

var x = result[0];
var y = result[0](4);
var z = x(4)
x(4)("Number is "); //Number is 4
y("More rhymes with "); //More rhymes with 4
z("I've never had charcoal icecream b"); //I've never had charcoal icecream b4

At what letter would it be considered currying? Thanks in advance.

Think of currying as something you do to a function rather than a way to write functions. The goal is to take a function with n arguments (where n > 1) and return a chain of functions that take exactly 1 argument each.

function doSomething(a,b,c) { /* ... */}
var f2 = curry(doSomething); // <-- transforms doSomething to a curried function
var f3 = f2("a");
var f4 = f3("b");
var finalOutput = 4("c");

// or
var curried = curry(doSomething);
var answer = curried("a")("b")("c");

When you’re using an IIFE, you can have as many functions as you want, but since you’re not changing the arity (number of arguments) of another function, it’s not considered currying.

(function(first) {
    return function() {
        // no other arguments
    }
})("dog")