Skip to main content
Ben Nadel at RIA Unleashed (Nov. 2009) with: Chris Lee and Geoff Bergey
Ben Nadel at RIA Unleashed (Nov. 2009) with: Chris Lee Geoff Bergey

Mutating An Array During .forEach() Iteration In JavaScript

By
Published in Comments (5)

As you know, I've been trying to learn a lot more about Redux lately. When I learn a new framework or library, I often spend some time looking at the source code so that I can get a better sense of what is actually going on under the hood. And, as I was looking at Redux's implementation of the .dispatch() method, I noticed something interesting: Dan Abramov was calling .slice() on the collection of listeners before invoking the callbacks. At the time, I assumed that this had to do with safety; but, it made me realize that I don't know enough about the Array.prototype.forEach() method in JavaScript. So, I wanted to take a look at how it behaves when the bound collection is mutated during iteration.

Run this demo in my JavaScript Demos project on GitHub.

To test this, I created a very simple demo in which I am using .forEach() to iterate over a known collection. Then, as I iterate, I'm both adding and deleting values from the collection. I do this twice - once without the .slice() method and once with it.

<!doctype html>
<html>
<head>
	<meta charset="utf-8" />

	<title>
		Mutating An Array During .forEach() Iteration In JavaScript
	</title>
</head>
<body>

	<h1>
		Mutating An Array During .forEach() Iteration In JavaScript
	</h1>

	<!-- Load scripts. -->
	<script type="text/javascript">

		var values = [ "o0", "o1", "o2", "o3", "o4" ];

		values.forEach(
			function iterator( value, index, collection ) {

				console.log( "Visiting:", value );

				if ( value === "o1" ) {

					// Delete current value.
					// --
					// NOTE: We have already logged this value out, so this action will
					// affect the length of the collection, but not the logging of this
					// particular item.
					values.splice( index, 1 );

				}

				if ( index === 3 ) {

					// Append new values.
					values.push( "n0" );
					values.push( "n1" );
					values.push( "n2" );
					values.push( "n3" );
					values.push( "n4" );

				}

			}
		);


		// --------------------------------------------------------------------------- //
		console.log( "- - - - - - - - - - - - - - - -" );
		// --------------------------------------------------------------------------- //


		var values = [ "o0", "o1", "o2", "o3", "o4" ];

		// This time, when iterating over the collection, we're going to iterate over
		// a DUPLICATE of the original collection using .slice().
		values.slice().forEach(
			function iterator( value, index, collection ) {

				console.log( "Visiting:", value );

				if ( value === "o1" ) {

					// Delete current value.
					values.splice( index, 1 );

				}

				if ( index === 3 ) {

					// Append new values.
					values.push( "n0" );
					values.push( "n1" );
					values.push( "n2" );
					values.push( "n3" );
					values.push( "n4" );

				}

			}
		);

	</script>

</body>
</html>

As you can see, the only difference between the two parts of the demo is that the latter half calls .slice() before .forEach(). This allows the second iterator to operate on a copy of the original collection, not the collection itself. And, when we run the above code, we get the following output:

What happens when you mutate a collection during the .forEach() iteration in ES5.

The results are really interesting. If you are unsure what is happening in the first half, basically the length of the .forEach() iteration is determine before the iteration starts. So, if the original collection has 5 items, for example, only the first 5 values will be visited even if the length of the collection increases during iteration.

Now, I didn't demonstrate this in the demo but, the .forEach() iteration is safe for deletion. Meaning, if the length of the collection shrinks during iteration, the .forEach() operator won't go out-of-bounds on the array. It will just stop at the last existing item.

In the latter half of the demo, all of the mutation is moot because the .forEach() operator is iterating over a copy of the collection. So, even as the original collection is mutated, the second iteration is unchanged as the copy remains static.

This is pretty cool stuff. Since I've dropped support for earlier IE (Internet Explorer) browsers, I've been trying to embrace the ES5 methods a lot more. And, it turns out, they're pretty cool. And, while I do like the idea of using .slice() to make .forEach() safe from mutation, I'm certainly not recommending that you do so all the time. But, if you know mutation can be an issue, it's quite an elegant safe-guard.

Want to use code from this post? Check out the license.

Reader Comments

7 Comments

@Ben,

You never stop investigate, that's great!
But I'm wondering how you can do & share all that stuff, do you sleep sometimes? :)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel