"There a number of ways to iterate using native Array.prototype methods. Can you describe a few of these ways and highlight differences between each?"

This has become one of my go to questions when assessing candidates for junior engineering roles, not least of which because there had been some not insignificant confusion among a couple members of my team a while ago on the topic. It's also a good question because the answer can demonstrate someone's understanding of some of the core functionality of JavaScript.

I've generally gotten good answers to this question, but always around when each method would be used and little discussion about the internal workings of each method. It thought it would be interesting to illustrate these internal differences by creating polyfills for some of these methods. What follows is the code for a few of the Array.prototype methods, example usage, and a little discussion.


Here is a polyfill for Array.prototype.map().

Array.prototype.$map = function $map(callback) {  
  var newArray = [];

  for (var index = 0; index < this.length; index++) {
    var item = this[index];

    // Always push return value of callback to the new array.
    newArray.push(callback(item, index, this));
  }

  return newArray;
};

var result = [1, 2, 3, 4, 5].$map(function (item) {  
  return item + 1;
});

console.log('$map result', result); // [2, 3, 4, 5, 6]  

What we'll end up seeing is that really the main difference between the methods discussed here, is just how the callback argument is treated. So in the example above, our polyfill equivalent of Array.prorotype.map() pushes the result of the callback to the newly returned array value. But below, in the polyfill for Array.prototype.filter(), the original array's values are only pushed onto the array to be returned, if each execution of callback evaluates as true.

Array.prototype.$filter = function $filter(callback) {  
  var newArray = [];

  for (var index = 0; index < this.length; index++) {
    var item = this[index];

    // Only push each item to the new array if the callback evaluates as true.
    if (callback(item, index, this) === true) {
      newArray.push(item);
    }
  }

  return newArray;
};

var result = [1, 2, 3, 4, 5].$filter(function (item) {  
  return item % 2 === 0;
});

console.log('$filter result', result); // [2, 4]  

So we've seen methods that directly use the return value of callback as well as evaluating the boolean equivalancy of the return value. There is another method that is only interested in the side-effects of the evaluation of callback. This method is Array.prototype.forEach(), which is illustrated here in polyfill form.

Array.prototype.$forEach = function $forEach(callback) {  
  var newArray = [];

  for (var index = 0; index < this.length; index++) {
    var item = this[index];

    // We're only interested in callback side-effects.
    callback(item, index, this);
  }

  return newArray;
};

var result = 0;

[1, 2, 3, 4, 5].$forEach(function (item) {
  result += item;
});

console.log('$forEach result', result); // 15  

However, in terms of the example given for the usage of the Array.prototype.forEach() polyfill, there actually is a more appropriate method to use. This is Array.prototype.reduce(). Reduce allows us to locally scope the initial value and manipulate it within the method, rather than needing to initialize it external to the method and rely on side effects to do our work.

Array.prototype.$reduce = function $reduce(callback, accumulator) {  
  for (var index = 0; index < this.length; index++) {
    var item = this[index];

    // We're operating directly on the accumulated value with each iteration.
    accumulator = callback(accumulator, item, index, this);
  }

  return accumulator;
};

var result = [0, 1, 2, 3, 4, 5].$reduce(function (acc, item) {  
  return acc += item;
}, 1);

console.log('$reduce result', result); // 16  

So those are a few examples of how writing polyfills for core JavaScript methods can lead to some interesting insights. In this case, we discovered that the Array.prototype methods we looked at really have a lot more in common with each other than we might think, given their very different behavior. The differences can be summed up in how each one uses the return value of their callback argument, or, in the case of Array.prototype.forEach(), the side effect of the callback.