I often wonder about language features that I know have a lot going on behind the scenes. In AS3, this commonly has me wondering about the for-in and for-each loops, which I use frequently. This article is about what happens to those loops when you change the array you’re iterating over during the iteration.

Again I have found an area that I should have long ago sat down and taken a few minutes to learn a little about the language. I have recently got around to sitting down and learning one more thing about some of AS3’s details. This detail will free you from a little bit of uncertainty, which is an awful thing for a programmer. At least it should be. How many times have I written the following loop in fear? How many times have you?

var len:int = myArray.length;
for (var i:int = 0; i < len; ++i)
{
	// If we have found the element to delete
	if (myArray[i].val == searchVal)
	{
		// Remove the element and adjust the iterator and length
		myArray.splice(i, 1);
		i--;
		len--;
	}
}

The adjustment code (i-- and len--) are critical to this loop functioning. Without the i--, this loop would skip the element after the one deleted it would be shifted forward and skipped on the next iteration. Without the len--, this loop would have gone past the end of the array and tried to access the val property of undefined, causing an error to be thrown at runtime. But, is all this necessary? Wouldn’t it be great if the for-in and for-each loops would handle all of this for us? Consider this vastly simplified loop:

for each (var cur:ValueHolder in myArray)
{
	if (cur.val == searchVal)
	{
		myArray.splice(myArray.indexOf(cur), 1);
	}
}

Firstly, while this loop is simpler it is also slower due to the expensive call to indexOf, which is not only O(n) but also dynamic. You would be better served to keep track of the index yourself, but it wouldn’t make much of a difference with small arrays. So, how about the correctness of this loop? Will it work as the first loop and remove all elements of the array whose val property matches searchVal? No. It essentially suffers the same problem we would have suffered in the first loop had we not prudently included the i-- adjustment. The for-each loop will, however, not go past the end of the array. Here is some explanatory code:

 
var a:Array = [2,4,6,8];
trace("a before the loop: [" + a + "]\n");
for each (var val:Number in a)
{
	trace("before: val=" + val + ", a=[" + a + "]");
	if (val == 4)
	{
		a.splice(a.indexOf(val), 1);
	}
	trace("before: val=" + val + ", a=[" + a + "]\n");
}
trace("a after the loop: [" + a + "]");

The output looks like this:

a after the loop: [2,4,6,8]
 
before: val=2, a=[2,4,6,8]
before: val=2, a=[2,4,6,8]
 
before: val=4, a=[2,4,6,8]
before: val=4, a=[2,6,8]
 
before: val=8, a=[2,6,8]
before: val=8, a=[2,6,8]
 
a after the loop: [2,6,8]

The loop’s body correctly removes the 4 element from the array. The next iteration the current value is 8, which indicates that the 6 was skipped. This is just what would have happened in our first for loop had we not included the i-- adjustment. I should mention that the same would occur had we used the for-in loop.

This is, unfortunately, a case where the for-in and for-each loops have failed us. The fear leading me to use a plain old for loop seems to be doubly-justified: the loop is faster because I always have the index to splice out and also works properly. There may be cases where you want this skipping behavior. It’s certainly something I plan to keep in mind as I change the arrays I’m looping over with for-in and for-each loops. If I come up with something clever, I’ll post it here.

Now, let us turn to additions to the array. Consider this minor alteration to the above loop:

var a:Array = [2,4,6,8];
trace("a before the loop: [" + a + "]\n");
for each (var val:Number in a)
{
	trace("before: val=" + val + ", a=[" + a + "]");
	if (val == 4)
	{
		a.push(10);
	}
	trace("before: val=" + val + ", a=[" + a + "]\n");
}
trace("a after the loop: [" + a + "]");

This code adds the 10 element to the end of the array upon finding the 4 element. Here, the for-each (and for-in) loop works out perfectly, just as you would expect from an implementation that adjusts for length (len++) but not for i (i-- or i++):

a before the loop: [2,4,6,8]
 
before: val=2, a=[2,4,6,8]
before: val=2, a=[2,4,6,8]
 
before: val=4, a=[2,4,6,8]
before: val=4, a=[2,4,6,8,10]
 
before: val=6, a=[2,4,6,8,10]
before: val=6, a=[2,4,6,8,10]
 
before: val=8, a=[2,4,6,8,10]
before: val=8, a=[2,4,6,8,10]
 
before: val=10, a=[2,4,6,8,10]
before: val=10, a=[2,4,6,8,10]
 
a after the loop: [2,4,6,8,10]

Lovely! On this high note, let’s try adding to the beginning:

var a:Array = [2,4,6,8];
trace("a before the loop: [" + a + "]\n");
for each (var val:Number in a)
{
	trace("before: val=" + val + ", a=[" + a + "]");
	if (val == 4)
	{
		a.unshift(10);
	}
	trace("before: val=" + val + ", a=[" + a + "]\n");
}
trace("a after the loop: [" + a + "]");

This again confirms that i is not adjusted. Because of this, we never advance past the 4 element. Since we keep finding the condition that triggers adding the 10 element to the beginning of the array, we have inadvertently written an infinite loop! Here is the beginning of the output:

a before the loop: [2,4,6,8]
 
before: val=2, a=[2,4,6,8]
before: val=2, a=[2,4,6,8]
 
before: val=4, a=[2,4,6,8]
before: val=4, a=[10,2,4,6,8]
 
before: val=4, a=[10,2,4,6,8]
before: val=4, a=[10,10,2,4,6,8]
 
before: val=4, a=[10,10,2,4,6,8]
before: val=4, a=[10,10,10,2,4,6,8]
 
before: val=4, a=[10,10,10,2,4,6,8]
before: val=4, a=[10,10,10,10,2,4,6,8]
 
before: val=4, a=[10,10,10,10,2,4,6,8]
before: val=4, a=[10,10,10,10,10,2,4,6,8]
 
before: val=4, a=[10,10,10,10,10,2,4,6,8]
before: val=4, a=[10,10,10,10,10,10,2,4,6,8]
 
...

So, in two of the three cases the for-in and for-each loops fail us. Keep in mind what’s going on behind the scenes and it will be clear to you if your loop is destined for success or failure. You’ll also have removed some of the “magic” surrounding Flash and have a better grip on why you’re getting the results you’re getting. So, I once again bid you happy looping!