Why is this JavaScript map NOT an Infinite Loop?

Viewed 1244

I'm learning JavaScript. I wrote this code to learn the map function. But then I got confused as to why is this not mapping over it continuously as with each map sequence a new element is pushed to the array. Shouldn't it continue to push new elements as it is mapping over ? Why does the map function only run for the original three elements and not for the new pushed ones?

I tried to debug it in the node environment and the arr variable goes in a closure. I know what a closure but I'm not able to understand what is going on here.

let array = [1, 2, 3];

array.map((element) => {
  array.push(10);
  console.log(element);
});

I expect that the output should be 1,2,3,10,10,10,10,10,10,10,10......10

But the actual output is only 1,2,3.

3 Answers

To quote from MDN:

The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback. If existing elements of the array are changed, their value as passed to callback will be the value at the time map visits them.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map#Description

So, it behaves like that because that is how it is designed. And it is designed that way, amongst other reasons, to prevent infinite loops!

map is a function from the Functional Programming world, where immutability is an important principle. According to this principle, if you call map on an input (and other variables don't change) you will always get exactly the same result. Allowing modification of the input breaks immutability.

Because it does not mutate the array (see Array​.prototype​.map()).

Instead, it returns a new array with the results of calling a provided function on every element in the calling array.

In the following snippet, what mutates the array is the call to array.push(10); three times (once per element in the original array) and not the map function itself.

let newArray = array.map((element) => {
    array.push(10); // <-- here you mutate the array
    console.log(element);
});

An important quote from the mentioned documentation (and key point here is):

The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback.


In the following snippet you can see an example of how to properly use the map function:

let array = [1,2,3];
let newArray = array.map(element => element + 10);  // sum 10 to every element

console.log('original: ', array); // original:  [1,2,3]
console.log('new one: ', newArray) // new one:  [11,12,13]


One last thought (from the docs too), taking as reference the code you posted:

Since map builds a new array, using it when you aren't using the returned array is an anti-pattern; use forEach or for-of instead.

Signs you shouldn't be using map:

  • A) You're not using the array it returns, and/or

  • B) You're not returning a value from the callback.

Why is exactly as seen on MDN:

var new_array = arr.map(function callback(currentValue[, index[, array]]){}

The range of elements processed by map is set before the first invocation of callback. Elements which are appended to the array after the call to map begins will not be visited by callback.

(Thank you to Joe's post above from MDN for this quote.)

Once map is called it then takes the array at that moment as it's parameter; once it's been passed then any changes are irrelevant to the previous variable itself.

See below:

let array = [1, 2, 3];

array.map((element) => {
  array.push(10);
  console.log(element);
});

console.log(array)

Related