Why is using "for...in" for array iteration a bad idea?
Solution 1
The reason is that one construct:
var a = []; // Create a new empty array.
a[5] = 5; // Perfectly legal JavaScript that resizes the array.
for (var i = 0; i < a.length; i++) {
// Iterate over numeric indexes from 0 to 5, as everyone expects.
console.log(a[i]);
}
/* Will display:
undefined
undefined
undefined
undefined
undefined
5
*/
can sometimes be totally different from the other:
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
console.log(x);
}
/* Will display:
5
*/
Also consider that JavaScript libraries might do things like this, which will affect any array you create:
// Somewhere deep in your JavaScript library...
Array.prototype.foo = 1;
// Now you have no idea what the below code will do.
var a = [1, 2, 3, 4, 5];
for (var x in a){
// Now foo is a part of EVERY array and
// will show up here as a value of 'x'.
console.log(x);
}
/* Will display:
0
1
2
3
4
foo
*/
Solution 2
The for-in
statement by itself is not a "bad practice", however it can be mis-used, for example, to iterate over arrays or array-like objects.
The purpose of the for-in
statement is to enumerate over object properties. This statement will go up in the prototype chain, also enumerating over inherited properties, a thing that sometimes is not desired.
Also, the order of iteration is not guaranteed by the spec., meaning that if you want to "iterate" an array object, with this statement you cannot be sure that the properties (array indexes) will be visited in the numeric order.
For example, in JScript (IE <= 8), the order of enumeration even on Array objects is defined as the properties were created:
var array = [];
array[2] = 'c';
array[1] = 'b';
array[0] = 'a';
for (var p in array) {
//... p will be "2", "1" and "0" on IE
}
Also, speaking about inherited properties, if you, for example, extend the Array.prototype
object (like some libraries as MooTools do), that properties will be also enumerated:
Array.prototype.last = function () { return this[this.length-1]; };
for (var p in []) { // an empty array
// last will be enumerated
}
As I said before to iterate over arrays or array-like objects, the best thing is to use a sequential loop, such as a plain-old for
/while
loop.
When you want to enumerate only the own properties of an object (the ones that aren't inherited), you can use the hasOwnProperty
method:
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
// prop is not inherited
}
}
And some people even recommend calling the method directly from Object.prototype
to avoid having problems if somebody adds a property named hasOwnProperty
to our object:
for (var prop in obj) {
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// prop is not inherited
}
}
Solution 3
There are three reasons why you shouldn't use for..in
to iterate over array elements:
for..in
will loop over all own and inherited properties of the array object which aren'tDontEnum
; that means if someone adds properties to the specific array object (there are valid reasons for this - I've done so myself) or changedArray.prototype
(which is considered bad practice in code which is supposed to work well with other scripts), these properties will be iterated over as well; inherited properties can be excluded by checkinghasOwnProperty()
, but that won't help you with properties set in the array object itselffor..in
isn't guaranteed to preserve element orderingit's slow because you have to walk all properties of the array object and its whole prototype chain and will still only get the property's name, ie to get the value, an additional lookup will be required
Solution 4
Because for...in enumerates through the object that holds the array, not the array itself. If I add a function to the arrays prototype chain, that will also be included. I.e.
Array.prototype.myOwnFunction = function() { alert(this); }
a = new Array();
a[0] = 'foo';
a[1] = 'bar';
for(x in a){
document.write(x + ' = ' + a[x]);
}
This will write:
0 = foo 1 = bar myOwnFunction = function() { alert(this); }
And since you can never be sure that nothing will be added to the prototype chain just use a for loop to enumerate the array:
for(i=0,x=a.length;i<x;i++){
document.write(i + ' = ' + a[i]);
}
This will write:
0 = foo 1 = bar
Solution 5
As of 2016 (ES6) we may use for…of
for array iteration, as John Slegers already noticed.
I would just like to add this simple demonstration code, to make things clearer:
Array.prototype.foo = 1;
var arr = [];
arr[5] = "xyz";
console.log("for...of:");
var count = 0;
for (var item of arr) {
console.log(count + ":", item);
count++;
}
console.log("for...in:");
count = 0;
for (var item in arr) {
console.log(count + ":", item);
count++;
}
The console shows:
for...of:
0: undefined
1: undefined
2: undefined
3: undefined
4: undefined
5: xyz
for...in:
0: 5
1: foo
In other words:
for...of
counts from 0 to 5, and also ignoresArray.prototype.foo
. It shows array values.for...in
lists only the5
, ignoring undefined array indexes, but addingfoo
. It shows array property names.
lYriCAlsSH
for (i =o; i < 24; i++) { window.document.write("It's midnight..."); } will edit when sun rises...
Updated on August 15, 2022Comments
-
lYriCAlsSH over 1 year
I've been told not to use
for...in
with arrays in JavaScript. Why not?-
mmurch over 13 yearsI saw the recent question where someone said that to you, but they only meant for Arrays. It is considered bad practice for iterating through arrays but not necessarily for iterating through members of an object.
-
Mark Schultheiss almost 13 yearsLots of answers with "for" loops such as 'for (var i=0; i<hColl.length; i++) {}' compare to 'var i=hColl.length; while (i--) {}' which, when it is possible to use the latter form it is substantially faster. I know this is tangential but thought I would add this bit.
-
ma11hew28 over 11 years@MarkSchultheiss but that's reverse iteration. Is there another version of forward iteration that's faster?
-
Mark Schultheiss over 11 years@MattDiPasquale - yes, it is reverse, however given a known limit, it allows optimum processing especially in older browsers by iteration over a known set vs an unknown set length by setting the limit first. It is the nature of an interpreted language.
-
WynandB about 11 years@MarkSchultheiss Good comment. One could also write the
for
loop asfor (var i = hColl.length; i--;) {}
, which should have a similar performance profile as the reversewhile
loop. -
Mark Schultheiss about 11 years@Wynand use
var i = hCol1.length; for (i;i;i--;) {}
cache the i as it will make a difference, and simplify the test. - the older the browser, the more difference betweenfor
andwhile
ALWAYS cache the "i" counter - and of course negative does not always fit the situation, and the negative whileobfuscate
the code a bit for some people. and notevar i = 1000; for (i; i; i--) {}
andvar b =1000 for (b; b--;) {}
where i goes 1000 to 1 and b goes 999 to 0. - the older the browser, the more the while tends to favor for performance. -
Mathieu Amiot almost 11 yearsYou can also be clever.
for(var i = 0, l = myArray.length; i < l; ++i) ...
is the fastest and best you can get with forward iteration.
-
-
vava over 15 yearsIn firefox 3 you could also use either arr.forEach or for(var [i, v] in Iterator(arr)) {} but neither of those works in IE, although you can write forEach method yourself.
-
JacquesB over 15 yearsThis answer is wrong. "lenght" will not be included in for-in iteration. It is only properties you add yourself which is included.
-
Pointy over 13 yearsNot enough - it's perfectly OK to add arbitrary named properties to array instances, and those will test
true
fromhasOwnProperty()
checks. -
JAL over 13 yearsGood point, thanks. I've never been silly enough to do that to an Array myself, so I haven't considered that!
-
JAL over 13 yearsJust a note about SO - there is no 'above' because comments change order on the page all the time. So, we don't really know which comment you mean. It's good to say "in x person's comment" for this reason.
-
Chris Morgan over 13 yearsRemember to use
(var x in a)
rather than(x in a)
- don't want to create a global. -
Chris Morgan over 13 yearsSee also David Humphrey's post Iterating over Objects in JavaScript Quickly - for array's
for..in
is much slower than "normal" loops. -
Scott Rippey over 13 yearsQuestion about the last point about "hasOwnProperty": If someone overrides "hasOwnProperty" on an object, you'll have problems. But won't you have the same problems if someone overrides "Object.prototype.hasOwnProperty"? Either way they're screwing you up and it's not your responsibility right?
-
Stewart about 13 yearsFirst issue isn't a reason it's bad, only a difference in semantics. Second issue seems to me a reason (on top of clashes between libraries doing the same) that altering the prototype of a built-in datatype is bad, rather than that for..in is bad.
-
Kenan Banks about 13 years@Stewart. The 'difference in semantics' is actually a flaw in Javascript. The broken semantics mean the code doesn't do what you think it will, and that's why the construct is typically avoided.
-
Stewart about 13 yearsArrays in JS are associative - they just can be used as linear arrays. As such, the whole point of using for..in is to iterate over the array's keys, whatever they may be.
-
Martijn about 13 years@Stewart: All objects in JS are associative. A JS Array is an object, so yes, it’s associative too, but that’s not what it’s for. If you want to iterate over an object's keys, use
for (var key in object)
. If you want to iterate over an array’s elements, however, usefor(var i = 0; i < array.length; i += 1)
. -
Tim Goodman about 13 yearsI corrected
for(int i=0
tofor(var i=0
. The first version produces a not-very-helpful "missing semi-colon" error (at least in Firefox). -
Stewart about 13 yearsSo using built-in arrays as associative arrays is abusing the JS object model? Is there any official recommendation on what to use for AAs instead?
-
stivlo over 12 yearsYou said for the first example, that it Iterates over numeric indexes from 0 to 4, as everyone expects, I expect it to iterate from 0 to 5! Since if you add an element in position 5, the array will have 6 elements (5 of them undefined).
-
Admin about 12 yearsThis overall presents a problem in javascript, since the point of something like for..in is to be key-agnostic on objects or arrays. Quite the opposite, if you add something to array[5] you expect the array to have one item, with key 5, not five blank items that you didn't insert. You expect there to be in the array what you put in it! Unless you told the array explicitly to have five items, key '5' realistically should not indicate the number of items in the array. But this relates to what an array represents; in JSON arrays do not really have keys; to use the term key is misleading.
-
Konrad Borowski about 12 yearsYou can use prefix
+
instead ofparseInt
unless you really need integer or ignore invalid characters. -
cHao almost 12 yearsProbably doesn't matter a whole lot. Array elements are properties of an Array-based or Array-like object, and all object properties have string keys. Unless your JS engine somehow optimizes it, even if you used a number it'd end up being turned into a string for the lookup.
-
Derek 朕會功夫 almost 12 yearsAlso, using
parseInt()
is not recommended. TryparseInt("025");
and it will fail. -
nnnnnn over 11 years@stivlo - if you add an element only in position 5, the array will have 1 element as the
for..in
loop shows. There's a difference between an element that doesn't exist and so returnsundefined
when you try to access it and an element that does exist and returnsundefined
because that value has been explicitly assigned. -
metamatt over 11 yearsI think the examples would be even clearer if you set a[5] to something other than 5, say, a[5]=42. The fact that the second example ("for (x in a)" only enumerates one value is not surprising to me; the fact that the value it enumerates is 5 instead of 42 is surprising to me (coming from other languages with a similar construct that enumerates list contents instead of array indices).
-
WynandB about 11 years@Pointy I haven't tested this, but perhaps this can be overcome by using an
isNaN
check on each property name. -
Pointy about 11 years@Wynand interesting idea; however I don't really see why it's worth the trouble when iterating with a simple numeric index is so easy.
-
August about 11 yearsIn the first example.. Would it be more efficient to call a.length only once (and set to a var) prior to the loop? vs each time the loop iterates?
-
Kenan Banks about 11 yearsAugustum - yes, it would be more efficient. But it would also be more code, and would therefore illustrate the concept under question less directly.
-
IAmTimCorey about 11 years@Derek朕會功夫 - you can definitely use
parseInt
. The issue is if you don't include the radix, older browsers might try to interpret the number (thus 025 becomes octal). This was fixed in ECMAScript 5 but it still happens for numbers starting with "0x" (it interprets the number as hex). To be on the safe side, use the radix to specify the number like soparseInt("025", 10)
- that specifies base 10. -
rjmunro almost 11 yearsYou say
for..in
is not bad practice, but it can be misused. Have you got a real world example of good practice, where you really did want to go through all of an objects properties including inherited properties? -
szmoore about 10 yearsRegardless of any performance issues, if you are new to JavaScript, use
var i in a
and expect the index to be an integer, then doing something likea[i+offset] = <value>
will be putting values in completely the wrong places. ("1" + 1 == "11"). -
Qwerty about 10 years
Object.keys(a).forEach( function(item) { console.log(item) } )
takes only real attributes, not those set in prototype. -
Qwerty about 10 years
Object.keys(a).forEach( function(item) { console.log(item) } )
iterate over an array of own property keys, not those inherited from prototype. -
Lior about 10 yearsTrue, but like the for-in loop, it won't necessarily be in the right index order. Also, it won't work on older browsers not supporting ES5.
-
Qwerty about 10 yearsYou can teach those browsers
array.forEach
by inserting certain code in your scripts. See Polyfill developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… -
Lior about 10 yearsOf course, but then you are manipulating the prototype, and that is not always a good idea...and still, you have the issue of the order...
-
Nathan Wall almost 10 years@ScottRippey: If you want to take it there: youtube.com/watch?v=FrFUI591WhI
-
doldt about 9 years@WynandB sorry for the bump, but I feel a correction is in order:
isNaN
is for checking whether a variable is the special value NaN or not, it cannot be used to check for 'things other than numbers' (you can go with a regular typeof for that). -
a better oliver almost 9 years
for ... in
works with objects. There's no such thing as auto-detection. -
NiCk Newman almost 9 yearsNice explanation Pointy. Just curious. If I had an array that was inside an object under multiply properties and did
for in
, as compared to a regular for loop, those arrays would get iterated over? (which would in essence, slow performance, right?) -
Pointy almost 9 years@NiCkNewman well the object you reference after
in
in afor ... in
loop will just -
NiCk Newman almost 9 yearsI see. Just curious because I have objects and arrays inside my main game object and was wondering if for inning that would be more painful then simply a regular for loop on the indexes.
-
Pointy almost 9 years@NiCkNewman well the theme of this whole question is that you just should not use
for ... in
on arrays; there are many good reasons not to. It's no so much a performance issue as a "make sure it doesn't break" issue. -
NiCk Newman almost 9 yearsWell, my objects are stored in an array technically, that's why I was worried, something like:
[{a:'hey',b:'hi'},{a:'hey',b:'hi'}]
, but yea, I understand. -
chuck258 over 8 years@Martijin: Instead of the rather long and verbose
for(var i = 0; i < array.length; i += 1)
pattern, there is a newfor let value of object
function in the ES6 standard which is already supported in most modern browsers See the MDN page -
equiman about 7 yearswith this answer I found that can access the value with
for (var p in array) { array[p]; }
-
MKPS almost 7 yearsUnder first example, in output part (
Will display:
), missed oneundefined
. Correct 5, not 4undefined
-s, I belive. P.s. I can't edit posts. -
KnowGe almost 7 yearsI respect you my friend. You saved me at 10 PM after one week digging everything. Really thank you !!!
-
Jonathan002 over 6 yearsWhy doesn't the
for...in
iterate over all standard prototypes instead of just newprototype
created like foo? -
adiga about 5 years@Jonathan002 I wondered the same. So,
for...in
iterates only over enumerable properties. AndArray.prototype
methods are defined usingObject.defineProperty
which setsenumerable
tofalse
by default. If you were to useObject.defineProperty(Array.prototype, 'foo', { value: 1 } )
, thenfoo
won't be displayed -
Gershom Maes almost 4 yearsIf you find a library which does something like
Array.prototype.foo = '...'
, you should report a bug as this code can have any number of unexpected side-effects. The correct way to do this isObject.defineProperty(Array.protoype, 'foo', { enumerable: false, value: '...' })
! -
Isaac Pak almost 4 yearsSo a better statement in this case is: "Don't use for-in on arrays because it will include anything you add to the prototype"?
-
CertainPerformance over 3 yearsAlso, the order of iteration is not guaranteed by the spec This is incorrect as of at least ES2015 and maybe before. Array indicies are guaranteed to be iterated over in ascending numeric order.