Why can the 'iterator' variable within a for...of loop be declared as const?

In the article about the for…of loop, the following code is presented:

const fruits = ['oranges', 'apples', 'grapes'];
 
for (const fruit of fruits) {
  console.log(fruit);
}

I am confused on why ‘fruit’ can be a const variable. As it changes with each iteration, isn’t this against the whole purpose of the const variable declaration?

Hey Kevin!
I know it’s confusing right? But it is actually very simple.

What you’re declaring with const is just that fruit is a single item of fruits, whatever it’s going to be it’s value, fruit will never change, is always going to be an item of the array fruits.

Check this thread for a longer explanation of how const declaration works:

Hope this solves your doubt :slight_smile:

Thank you for your answer! :slight_smile:
Unfortunately it adds to my confusion :sweat_smile:

In the provided link it states that “The value of a constant cannot change through re-assignment…”, but clearly ‘fruit’ is re-assigned a different element of ‘fruits’ on each iteration. The only way i can make sense of this ‘re-assignment’ is if ‘fruit’ is deleted/runs out-of-scope at the end of each iteration of the for…of loop and gets re-declared and assigned before each iteration of the for…of loop.

Is this the case? If yes, this leads to another question: Has this suspected re-declaration in the for…of loop some performance implications for more complex arrays were the elements itself are bigger objects?
For the normal for loop, the iterator is only declared and assigned once before the first iteration and re-assigned after each iteration, so memory stays allocated throughout the loop. With re-declaration before each iteration memory should be allocated before and deallocated after each iteration (sound like overhead to me).

The value of a constant cannot change through re-assignment

means that it can change, but not through re-assignment.

a constant cannot be re-declared

means that if you declare it, you cannot do it again.

However if you do something like this, you can declare it multiple times:

const array = [0, 1, 2, 3];
let i = 0;
while (i<array.length){
    const a = array[i];
    console.log(a);
    i++
};
//output is: 0 1 2 3 3

Why? In this example we are exploiting the “out-of-scope” thing you say.

So to answer your questions:

Is this the case?

I don’t know if the for…of… loop uses this logic. It may be, the MDN page about this gives a couple of examples about when to use const and when to use let. It would make sense if it would work this way though.

Has this suspected re-declaration in the for…of loop some performance implications for more complex arrays were the elements itself are bigger objects?

I cannot say by experience as I never found myself in this situation of handling such large arrays. From a quick research on the web I didn’t find any definitive statement about this. Try searching it yourself, maybe you’re luckier than me. In any case, you can test this thesis yourself anytime, just open your browser console, load a heavy array and loop through it with different methods, see if there are any statistical relevant temporal differences and let us know!

I’ve tried to test it out by myself using the following code (testing on Edge Version 90.0.818.62):

//Initialize Variables
let start;
let finish;
let numberOfElements = 9999999
let forOfConstAverage = 0;
let forOfLetAverage = 0;
let forAverage = 0;
let storage = 0;
let bigArray = [];
let bigArrayWithBigElements = [];

//Populate Array with integer elements
for(let i = 0; i < numberOfElements; i++) bigArray.push(i);


//Measure each for loop 100 times and calculate the average
for(let i = 1; i <= 100; i++){
start = performance.now();
for(const arr of bigArray) continue;
finish = performance.now();

forOfConstAverage = ((forOfConstAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let arr of bigArray) continue;
finish = performance.now();

forOfLetAverage = ((forOfLetAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let i = 0; i < bigArray.length; i++) continue;
finish = performance.now();

forAverage = ((forAverage * (i-1)) + (finish - start))/ i;

}

console.log("Results for big Array with integer elements");
console.log(`For...of const average time: ${forOfConstAverage}`);
console.log(`For...of let average time: ${forOfConstAverage}`);
console.log(`For average time: ${forAverage}`);


//Reset Variables
forOfConstAverage = 0;
forOfLetAverage = 0;
forAverage = 0;
storage = 0;

//Populate Array with Array Elements
for(let i = 0; i < numberOfElements; i++) bigArrayWithBigElements.push(bigArray);

//Check 100 times for a equally big Array with big array elements and calculate average
for(let i = 1; i <= 100; i++){
start = performance.now();
for(const arr of bigArrayWithBigElements) continue;
finish = performance.now();

forOfConstAverage = ((forOfConstAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let arr of bigArrayWithBigElements) continue;
finish = performance.now();

forOfLetAverage = ((forOfLetAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let i = 0; i < bigArrayWithBigElements.length; i++) continue;
finish = performance.now();

forAverage = ((forAverage * (i-1)) + (finish - start))/ i;

}

console.log("Results for big Array with array elements");
console.log(`For...of const average time: ${forOfConstAverage}`);
console.log(`For...of let average time: ${forOfConstAverage}`);
console.log(`For average time: ${forAverage}`);

//Reset Variables
forOfConstAverage = 0;
forOfLetAverage = 0;
forAverage = 0;
storage = 0;

//Construct a really long string
let str = 'AB'
for(let i = 0; i <= 26; i++) str += str;

//populate Array with really long string
for(let i = 0; i < numberOfElements; i++) bigArrayWithBigElements.push(str);

//Check 100 times for a equally big Array with big string elements and calculate averages
for(let i = 1; i <= 100; i++){
start = performance.now();
for(const arr of bigArrayWithBigElements) continue;
finish = performance.now();

forOfConstAverage = ((forOfConstAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let arr of bigArrayWithBigElements) continue;
finish = performance.now();

forOfLetAverage = ((forOfLetAverage * (i - 1)) + (finish - start))/i

start = performance.now();
for(let i = 0; i < bigArrayWithBigElements.length; i++) continue;
finish = performance.now();

forAverage = ((forAverage * (i-1)) + (finish - start))/ i;

}

console.log("Results for big Array with string elements");
console.log(`For...of const average time: ${forOfConstAverage}`);
console.log(`For...of let average time: ${forOfConstAverage}`);
console.log(`For average time: ${forAverage}`);

Turns out there is a difference in execution time:

Results for big Array with integer elements
For…of const average time: 7.229050000896673
For…of let average time: 7.229050000896673
For average time: 4.609250000212341
Results for big Array with array elements
For…of const average time: 7.185899998876269
For…of let average time: 7.185899998876269
For average time: 4.627399998717012
Results for big Array with string elements
For…of const average time: 16.57684999576304
For…of let average time: 16.57684999576304
For average time: 4.694799997378144

For array elements that are arrays, only a reference to the element is stored in the iterator variable (i’ve tested this), hence the similar runtimes for bigArray with integer elements and bigArray with array elements. For strings however it seems like they are indeed copied to the iterator variable.

Overall it looks like for…of is generally a little bit slower than a for loop.

1 Like

Amazing! :slight_smile:

A trick to populate an Array with less code is the .fill() method, I’ll leave you with the MDN page about it for future reference :wink:

1 Like