FossilsðŸ’ŧ CodingImplement Array Methods (map, filter, reduce, forEach, flat)
ðŸĢHatchlingJavaScriptPolyfillArrays

Implement Array Methods (map, filter, reduce, forEach, flat)

Polyfilling core array methods tests understanding of callbacks, accumulation patterns, `this` binding, and recursive flattening. These are the most frequently asked polyfill questions.

Implement Array Methods

Interview Question: "Implement map, filter, reduce, forEach, and flat from scratch."

Array.prototype.map

Array.prototype.myMap = function(callback, thisArg) {
  const result = new Array(this.length);
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      result[i] = callback.call(thisArg, this[i], i, this);
    }
  }
  return result;
};

Key details:

  • Returns a new array of the same length
  • callback.call(thisArg, ...) — the second argument to map sets this inside the callback
  • i in this — skips holes in sparse arrays: [1, , 3].map(x => x) preserves the hole
  • Callback receives (element, index, array)

Array.prototype.filter

Array.prototype.myFilter = function(callback, thisArg) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    if (i in this && callback.call(thisArg, this[i], i, this)) {
      result.push(this[i]);
    }
  }
  return result;
};

Key differences from map:

  • Result array can be shorter than input
  • Only pushes elements where callback returns truthy

Array.prototype.reduce

Array.prototype.myReduce = function(callback, initialValue) {
  let accumulator;
  let startIndex;
 
  if (arguments.length >= 2) {
    accumulator = initialValue;
    startIndex = 0;
  } else {
    if (this.length === 0) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    let found = false;
    for (let i = 0; i < this.length; i++) {
      if (i in this) {
        accumulator = this[i];
        startIndex = i + 1;
        found = true;
        break;
      }
    }
    if (!found) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
  }
 
  for (let i = startIndex; i < this.length; i++) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this);
    }
  }
 
  return accumulator;
};

Critical edge cases:

  • No initialValue + empty array → TypeError
  • No initialValue → first non-hole element becomes accumulator, iteration starts from next element
  • Reduce does NOT take a thisArg (unlike map/filter)
  • Sparse array holes are skipped
[1, 2, 3].myReduce((acc, val) => acc + val);      // 6
[1, 2, 3].myReduce((acc, val) => acc + val, 10);   // 16
[].myReduce((acc, val) => acc + val);               // TypeError

Array.prototype.forEach

Array.prototype.myForEach = function(callback, thisArg) {
  for (let i = 0; i < this.length; i++) {
    if (i in this) {
      callback.call(thisArg, this[i], i, this);
    }
  }
};

Key point: forEach returns undefined — it cannot be chained. It also cannot be stopped early (no break). Use a for...of loop if you need early exit.

Array.prototype.flat

Array.prototype.myFlat = function(depth = 1) {
  const result = [];
 
  function flatten(arr, currentDepth) {
    for (let i = 0; i < arr.length; i++) {
      if (!(i in arr)) continue;
 
      if (Array.isArray(arr[i]) && currentDepth < depth) {
        flatten(arr[i], currentDepth + 1);
      } else {
        result.push(arr[i]);
      }
    }
  }
 
  flatten(this, 0);
  return result;
};

Alternative iterative approach:

Array.prototype.myFlat = function(depth = 1) {
  let result = [...this];
  for (let d = 0; d < depth; d++) {
    const next = [];
    let didFlatten = false;
    for (const item of result) {
      if (Array.isArray(item)) {
        next.push(...item);
        didFlatten = true;
      } else {
        next.push(item);
      }
    }
    result = next;
    if (!didFlatten) break;
  }
  return result;
};
[1, [2, [3, [4]]]].myFlat();        // [1, 2, [3, [4]]]
[1, [2, [3, [4]]]].myFlat(2);       // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].myFlat(Infinity); // [1, 2, 3, 4]

Follow-Up: reduceRight

Array.prototype.myReduceRight = function(callback, initialValue) {
  let accumulator;
  let startIndex;
 
  if (arguments.length >= 2) {
    accumulator = initialValue;
    startIndex = this.length - 1;
  } else {
    if (this.length === 0) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = this[this.length - 1];
    startIndex = this.length - 2;
  }
 
  for (let i = startIndex; i >= 0; i--) {
    if (i in this) {
      accumulator = callback(accumulator, this[i], i, this);
    }
  }
 
  return accumulator;
};

Common Mistakes

  • Forgetting thisArg support (second argument to map/filter/forEach)
  • Not handling sparse arrays (i in this check)
  • Using arguments.length instead of checking initialValue !== undefined for reduce (the latter fails when undefined is explicitly passed as initial value)
  • Not handling Infinity depth for flat
  • Missing TypeError for reduce on empty array without initial value