Why is using "for...in" for array iteration a bad idea?

948,330

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't DontEnum; that means if someone adds properties to the specific array object (there are valid reasons for this - I've done so myself) or changed Array.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 checking hasOwnProperty(), but that won't help you with properties set in the array object itself

  • for..in isn't guaranteed to preserve element ordering

  • it'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 ignores Array.prototype.foo. It shows array values.

  • for...in lists only the 5, ignoring undefined array indexes, but adding foo. It shows array property names.

Share:
948,330
lYriCAlsSH
Author by

lYriCAlsSH

for (i =o; i &lt; 24; i++) { window.document.write("It's midnight..."); } will edit when sun rises...

Updated on August 15, 2022

Comments

  • lYriCAlsSH
    lYriCAlsSH over 1 year

    I've been told not to use for...in with arrays in JavaScript. Why not?

    • mmurch
      mmurch over 13 years
      I 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
      Mark Schultheiss almost 13 years
      Lots 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
      ma11hew28 over 11 years
      @MarkSchultheiss but that's reverse iteration. Is there another version of forward iteration that's faster?
    • Mark Schultheiss
      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
      WynandB about 11 years
      @MarkSchultheiss Good comment. One could also write the for loop as for (var i = hColl.length; i--;) {}, which should have a similar performance profile as the reverse while loop.
    • Mark Schultheiss
      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 between for and while ALWAYS cache the "i" counter - and of course negative does not always fit the situation, and the negative while obfuscate the code a bit for some people. and note var i = 1000; for (i; i; i--) {} and var 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
      Mathieu Amiot almost 11 years
      You 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
    vava over 15 years
    In 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
    JacquesB over 15 years
    This answer is wrong. "lenght" will not be included in for-in iteration. It is only properties you add yourself which is included.
  • Pointy
    Pointy over 13 years
    Not enough - it's perfectly OK to add arbitrary named properties to array instances, and those will test true from hasOwnProperty() checks.
  • JAL
    JAL over 13 years
    Good point, thanks. I've never been silly enough to do that to an Array myself, so I haven't considered that!
  • JAL
    JAL over 13 years
    Just 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
    Chris Morgan over 13 years
    Remember to use (var x in a) rather than (x in a) - don't want to create a global.
  • Chris Morgan
    Chris Morgan over 13 years
    See also David Humphrey's post Iterating over Objects in JavaScript Quickly - for array's for..in is much slower than "normal" loops.
  • Scott Rippey
    Scott Rippey over 13 years
    Question 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
    Stewart about 13 years
    First 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
    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
    Stewart about 13 years
    Arrays 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
    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, use for(var i = 0; i < array.length; i += 1).
  • Tim Goodman
    Tim Goodman about 13 years
    I corrected for(int i=0 to for(var i=0. The first version produces a not-very-helpful "missing semi-colon" error (at least in Firefox).
  • Stewart
    Stewart about 13 years
    So 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
    stivlo over 12 years
    You 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
    Admin about 12 years
    This 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
    Konrad Borowski about 12 years
    You can use prefix + instead of parseInt unless you really need integer or ignore invalid characters.
  • cHao
    cHao almost 12 years
    Probably 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 朕會功夫
    Derek 朕會功夫 almost 12 years
    Also, using parseInt() is not recommended. Try parseInt("025"); and it will fail.
  • nnnnnn
    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 returns undefined when you try to access it and an element that does exist and returns undefined because that value has been explicitly assigned.
  • metamatt
    metamatt over 11 years
    I 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
    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
    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
    August about 11 years
    In 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
    Kenan Banks about 11 years
    Augustum - yes, it would be more efficient. But it would also be more code, and would therefore illustrate the concept under question less directly.
  • IAmTimCorey
    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 so parseInt("025", 10) - that specifies base 10.
  • rjmunro
    rjmunro almost 11 years
    You 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
    szmoore about 10 years
    Regardless 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 like a[i+offset] = <value> will be putting values in completely the wrong places. ("1" + 1 == "11").
  • Qwerty
    Qwerty about 10 years
    Object.keys(a).forEach( function(item) { console.log(item) } ) takes only real attributes, not those set in prototype.
  • Qwerty
    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
    Lior about 10 years
    True, 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
    Qwerty about 10 years
    You 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
    Lior about 10 years
    Of 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
    Nathan Wall almost 10 years
    @ScottRippey: If you want to take it there: youtube.com/watch?v=FrFUI591WhI
  • doldt
    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
    a better oliver almost 9 years
    for ... in works with objects. There's no such thing as auto-detection.
  • NiCk Newman
    NiCk Newman almost 9 years
    Nice 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
    Pointy almost 9 years
    @NiCkNewman well the object you reference after in in a for ... in loop will just
  • NiCk Newman
    NiCk Newman almost 9 years
    I 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
    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
    NiCk Newman almost 9 years
    Well, 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
    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 new for let value of object function in the ES6 standard which is already supported in most modern browsers See the MDN page
  • equiman
    equiman about 7 years
    with this answer I found that can access the value with for (var p in array) { array[p]; }
  • MKPS
    MKPS almost 7 years
    Under first example, in output part (Will display:), missed one undefined. Correct 5, not 4 undefined-s, I belive. P.s. I can't edit posts.
  • KnowGe
    KnowGe almost 7 years
    I respect you my friend. You saved me at 10 PM after one week digging everything. Really thank you !!!
  • Jonathan002
    Jonathan002 over 6 years
    Why doesn't the for...in iterate over all standard prototypes instead of just new prototype created like foo?
  • adiga
    adiga about 5 years
    @Jonathan002 I wondered the same. So, for...in iterates only over enumerable properties. And Array.prototype methods are defined using Object.defineProperty which sets enumerable to false by default. If you were to use Object.defineProperty(Array.prototype, 'foo', { value: 1 } ), then foo won't be displayed
  • Gershom Maes
    Gershom Maes almost 4 years
    If 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 is Object.defineProperty(Array.protoype, 'foo', { enumerable: false, value: '...' })!
  • Isaac Pak
    Isaac Pak almost 4 years
    So 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
    CertainPerformance over 3 years
    Also, 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.