Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Karl Swedberg
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Karl Swedberg@kswedberg )

Mutating An Array During .forEach() Iteration In JavaScript

By Ben Nadel on

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.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader 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? :)

Reply to this Comment

@Bertrand,

Ha ha, some mornings it feels like that :D I woke up so tired today, but at least it's FRIDAY!!!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.