Avoid “for in” loops in JavaScript

Newcomers to JavaScript from other programming languages often neglect an important aspect that makes their code prone to subtle, difficult-to-detect bugs down the road. JavaScript’s "for in" loop is not your C-like-language’s “for each” construct. "for in" enumerates all properties of a JavaScript object, including inherited properties. 90% of the time you do not want to enumerate inherited properties, therefore 90% of the time you do not want to use "for in". Fortunately there are easy, powerful alternatives to "for in" provided natively by JavaScript. On top of that, there are enhanced solutions supplied by libraries.

Before we talk solutions though, let's detail why "for in" is problematic. In JavaScript, libraries or application code can easily tack on extra inherited properties to objects. Since "for in" enumerates these inherited properties too, surprise elements can be added that your code inside the loop body may not be able to handle.

Today, this code to determine the product of all elements in an array produces this output:

var nums = [1, 2, 42];

var product = 1;
var i, num;
for (i in nums) {
    num = nums[i];
    console.log(num);
    product *= num;
}
console.log(product);

// Output:
// 1 
// 2 
// 42 
// 84

Tomorrow, a library or other application code adds a stellar general-purpose utility method. Now this happens:

//in some other file...

Array.prototype.sum = function(){
    //(logic to calculate sum of array elements here)
};

//in original file...
var nums = [1, 2, 42];

var product = 1;
var i, num;
for (i in nums) {
    num = nums[i];
    console.log(num);
    product *= num;
}
console.log(product);

// Output:
// 1 
// 2 
// 42 
// function (){
//     //(logic to calculate sum of array elements here)
//     }
// NaN 

Next, let's examine a scenario where we're determining the product of all values in an object

Today, our code works:

var obj = {a:1, b:2, c:3};

var product = 1;
var prop, value;
for (prop in obj) {
    num = obj[prop];
    console.log(prop + ":" + num);
    product *= num;
}
console.log(product)

// Output:
// a:1 
// b:2 
// c:3 
// 6

A year later, it doesn't:

//in some other file..

Object.prototype.isEmpty = function(){
  //(logic to determine if an object has any properties)  
};

//in original file..

var obj = {a:1, b:2, c:3};

var product = 1;
var prop, value;
for (prop in obj) {
    num = obj[prop];
    console.log(prop + ":" + num);
    product *= num;
}
console.log(product)

// Output:
// a:1 
// b:2 
// c:3 
// isEmpty:function (){
//   //(logic to determine if an object has any properties)
// }
// NaN

We can never guarantee that our object's prototypes will remain pristine. We do not want subsequent developers to have to track down easily-preventable bugs. Accordingly, as responsible developers, we use these alternatives to JavaScript's "for in":

  • Iterating over arrays:
    • The native array.forEach instance method (available in IE9 and up)
      var nums = [1, 2, 42];
      
      Array.prototype.sum = function(){
          //(logic to calculate sum of array elements here)
      };
      
      var product = 1;
      nums.forEach(function(num){
          console.log(num);
          product *= num;
      });
      console.log(product);
      
      // Output:
      // 1
      // 2
      // 42
      // 84
      
    • Sugar JS's array.each instance method, an enhanced version of array.forEach
      var nums = [1, 2, 42];
      
      Array.prototype.sum = function(){
          //(logic to calculate sum of array elements here)
      };
      
      var product = 1;
      nums.each(function(num /*, optional_start_index */){
          console.log(num);
          product *= num;
          //optionally return false to stop iteration
      });
      console.log(product);
      
      // Output:
      // 1
      // 2
      // 42
      // 84
      
  • Enumerating objects:
    • The native Object.keys static method (available in IE9 and up)
      //in some other file..
      
      Object.prototype.isEmpty = function(){
        //(logic to determine if an object has any properties)  
      };
      
      //in original file..
      
      var obj = {a:1, b:2, c:3};
      
      var product = 1;
      var keys = Object.keys(obj);
      keys.forEach(function(key){
          num = obj[key];
          console.log(key + ":" + num);
          product *= num;
      });
      console.log(product)
      
      // Output:
      // a:1 
      // b:2 
      // c:3 
      // 6
      
    • Sugar JS's Object.keys static method, an enhanced version of the native method
      //in some other file..
      
      Object.prototype.isEmpty = function(){
        //(logic to determine if an object has any properties)  
      };
      
      //in original file..
      
      var obj = {a:1, b:2, c:3};
      
      var product = 1;
      Object.keys(obj, function(key, num){
          console.log(key + ":" + num);
          product *= num;
      });
      console.log(product)
      
      // Output:
      // a:1 
      // b:2 
      // c:3 
      // 6
      
  • Last and least:
    • If you REALLY want to use “for in”
      for (var prop in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, prop)) {
              // ^ yes, you really need to type all of that ^ 
              // // now, ‘prop’ is not inherited 
          }
      }
      

The fact that most of the alternatives force you to write the body of your loop as a function is a good thing — it makes it easier to test what happens to a single element of a collection in isolation. To test the "loop body", we'll rewrite the SugarJS Object.keys example with a named function instead of an anonymous function. Now that the function is named, we can reference it from a test.

//in some other file..

Object.prototype.isEmpty = function(){
  //(logic to determine if an object has any properties)  
};

//in original file..

var obj = {a:1, b:2, c:3};

var product = 1;

var printAndMultiply = function(key, num){
    console.log(key + ":" + num);
    product *= num;
};

Object.keys(obj,printAndMultiply);
console.log(product)

// Output:
// a:1 
// b:2 
// c:3 
// 6

//in some jasmine test file..
describe('printAndMultiply', function(){
    beforeEach(function(){
        product = 1;    
    });
    it('should multiply by two correctly', function(){
        printAndMultiply('some_key', 2);
        expect(product).toBe(2);
    });
    it('should multiply by three correctly', function(){
        printAndMultiply('some_key', 3);
        expect(product).toBe(3);
        printAndMultiply('some_key', 3);
        expect(product).toBe(9);
    });
});

For this same concern of testability, and others reasons detailed here, you should favor array.forEach over traditional numeric for loops (for( i =0; i < array.length; i++)) unless you need explicit control of incrementation and stop conditions.

Seasoned JavaScript programmers will note that many of the examples could have been written more succinctly with a reduce() function. While map, reduce, and filter functions are important, they are out of the scope of this post.

 

Computing, Programming Best Practices, Web Best PracticesPermalink

5 Responses to Avoid “for in” loops in JavaScript

  1. Fodagus says:

    No mention of jQuery’s .each? Sure jQuery is old, but it’s pretty Ubiquitous still.

    The other advantage to point out with these methods over using old-style numerically indexed for loops or filtering with hasOwnProperty is that they introduce the novice JS programmer to the idea of callbacks (and in some cases, callback chaining).

    This is a concept that begins to be more important as one delves in the Asynchronous World of AJAX and Node.js (and it’s ilk)…

    • carl says:

      Excellent point Fodagus! Thanks! I will add examples of jQuery’s each method. I hadn’t thought of using these iteration methods to introduce callbacks. I think you’re right, they would be a good teaching device for introducing functions as first class citizens – it’s easier to wrap your mind around synchronous stuff as a newcomer than it is to understand asynchronous stuff.

      • carl says:

        Fodagus, thank you for prompting me to investigate this. Unfortunately, it turns out that the jQuery each method does not filter out inherrited properties, as I show in this example.

        A jQuery bug report closed as 'wontfix' demonstrates that this is the intended behavior of $.each.

        Since we don't want to type this every time we iterate and enumerate…  

        if (Object.prototype.hasOwnProperty.call(obj, prop)) {
        
        }

        … I don't want to list it as a solution. Though, as you point out, it is an often-used function for iteration and enueration, so should we update the post body to highlight that it is subject to the same pitfalls as "for in" ?

  2. ee says:

    Nice!  JS is not my native language so I stumble across that for-in loop feature occasionally.  Usually I think, “Wait, can’t I use a for-in loop for this?” I look it up, realize it iterates over properties and I stop.

    At least, I think that is what happens each time…. 

Leave a Reply

Your email address will not be published. Required fields are marked *