DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on • Edited on

Polyfills for .forEach(), .map(), .filter(), .reduce() in JavaScript

Build Your Own Array Helpers — Simple vs. Advanced (Interview-Ready)

This markdown gives you two versions for each array method: a simple, easy-to-remember version for interviews, and a more advanced version with stronger edge-case handling. All methods accept a single callback (no thisArg).

Methods covered:

  • forEach
  • map
  • filter
  • reduce

Notes:

  • “Holes” are missing indices in sparse arrays (e.g., [1, , 3]). Native methods skip them when iterating. We’ll mirror that with if (i in arr).
  • In production, prefer the built-ins or well-tested polyfills and avoid modifying Array.prototype.

forEach

Simple (Interview-friendly)

  • Assumes normal arrays.
  • Skips holes to match native forEach.
  • Minimal validation.
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = function (callback) {
    if (typeof callback !== "function") {
      throw new TypeError("callback must be a function");
    }
    const arr = this;
    const len = arr.length;
    for (let i = 0; i < len; i++) {
      if (i in arr) { // skip holes
        callback(arr[i], i, arr);
      }
    }
    // returns undefined like native
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

["ali","hamza","jack"].myForEach((v, i) => console.log(i, v));
Enter fullscreen mode Exit fullscreen mode

Advanced (More robust)

  • Guards against null/undefined receiver.
  • Validates callback.
  • Skips holes, handles array-like via Object(this), normalizes length.
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = function (callback) {
    if (this == null) throw new TypeError("Array.prototype.myForEach called on null or undefined");
    if (typeof callback !== "function") throw new TypeError("callback must be a function");

    const O = Object(this);
    const len = O.length >>> 0; // normalize to uint32

    for (let i = 0; i < len; i++) {
      if (Object.prototype.hasOwnProperty.call(O, i)) { // skip holes
        callback(O[i], i, O);
      }
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

map

Simple (Interview-friendly)

  • Preserves holes and output indices by assigning into a pre-sized array.
  • Minimal validation.
if (!Array.prototype.myMap) {
  Array.prototype.myMap = function (callback) {
    if (typeof callback !== "function") {
      throw new TypeError("callback must be a function");
    }
    const arr = this;
    const len = arr.length;
    const out = new Array(len);
    for (let i = 0; i < len; i++) {
      if (i in arr) {           // skip holes when reading
        out[i] = callback(arr[i], i, arr);
      }
      // else leave hole in out
    }
    return out;
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

const mapped = [1, , 3].myMap(x => x * 10);
console.log(mapped);      // [10, <1 empty item>, 30]
console.log(1 in mapped); // false
Enter fullscreen mode Exit fullscreen mode

Advanced (More robust)

  • Guards against null/undefined receiver.
  • Validates callback.
  • Supports array-like via Object(this), normalizes length, skips holes, preserves holes in result.
if (!Array.prototype.myMap) {
  Array.prototype.myMap = function (callback) {
    if (this == null) throw new TypeError("Array.prototype.myMap called on null or undefined");
    if (typeof callback !== "function") throw new TypeError("callback must be a function");

    const O = Object(this);
    const len = O.length >>> 0;
    const A = new Array(len);

    for (let i = 0; i < len; i++) {
      if (Object.prototype.hasOwnProperty.call(O, i)) {
        A[i] = callback(O[i], i, O);
      }
      // else leave hole
    }
    return A;
  };
}
Enter fullscreen mode Exit fullscreen mode

filter

Simple (Interview-friendly)

  • Skips holes during iteration.
  • Minimal validation.
if (!Array.prototype.myFilter) {
  Array.prototype.myFilter = function (callback) {
    if (typeof callback !== "function") {
      throw new TypeError("callback must be a function");
    }
    const arr = this;
    const len = arr.length;
    const out = [];
    for (let i = 0; i < len; i++) {
      if (i in arr) {               // skip holes
        const v = arr[i];
        if (callback(v, i, arr)) out.push(v);
      }
    }
    return out;
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

console.log([1, , 3, 4].myFilter(v => v > 2)); // [3, 4]
Enter fullscreen mode Exit fullscreen mode

Advanced (More robust)

  • Guards against null/undefined receiver.
  • Validates callback.
  • Supports array-like via Object(this), normalizes length, skips holes.
if (!Array.prototype.myFilter) {
  Array.prototype.myFilter = function (callback) {
    if (this == null) throw new TypeError("Array.prototype.myFilter called on null or undefined");
    if (typeof callback !== "function") throw new TypeError("callback must be a function");

    const O = Object(this);
    const len = O.length >>> 0;
    const res = [];

    for (let i = 0; i < len; i++) {
      if (Object.prototype.hasOwnProperty.call(O, i)) {
        const v = O[i];
        if (callback(v, i, O)) res.push(v);
      }
    }
    return res;
  };
}
Enter fullscreen mode Exit fullscreen mode

reduce

Simple (Interview-friendly)

  • Handles initialValue correctly using arguments.length.
  • Skips holes.
  • Minimal validation.
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function (callback, initialValue) {
    if (typeof callback !== "function") {
      throw new TypeError("callback must be a function");
    }
    const arr = this;
    const len = arr.length;

    let i = 0;
    let acc;
    if (arguments.length > 1) {
      acc = initialValue;
    } else {
      // find first present element as initial acc
      while (i < len && !(i in arr)) i++;
      if (i >= len) throw new TypeError("Reduce of empty array with no initial value");
      acc = arr[i++];
    }

    for (; i < len; i++) {
      if (i in arr) {
        acc = callback(acc, arr[i], i, arr);
      }
    }
    return acc;
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

console.log([1,2,3].myReduce((a,v)=>a+v));      // 6
console.log([,2,,4].myReduce((a,v)=>a+v, 0));   // 6 (skips holes)
Enter fullscreen mode Exit fullscreen mode

Advanced (More robust)

  • Guards against null/undefined receiver.
  • Validates callback.
  • Supports array-like via Object(this), normalizes length, skips holes.
  • Throws on empty arrays without initialValue.
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function (callback, initialValue) {
    if (this == null) throw new TypeError("Array.prototype.myReduce called on null or undefined");
    if (typeof callback !== "function") throw new TypeError("callback must be a function");

    const O = Object(this);
    const len = O.length >>> 0;

    let k = 0;
    let acc;

    if (arguments.length > 1) {
      acc = initialValue;
    } else {
      while (k < len && !Object.prototype.hasOwnProperty.call(O, k)) k++;
      if (k >= len) throw new TypeError("Reduce of empty array with no initial value");
      acc = O[k++];
    }

    for (; k < len; k++) {
      if (Object.prototype.hasOwnProperty.call(O, k)) {
        acc = callback(acc, O[k], k, O);
      }
    }
    return acc;
  };
}
Enter fullscreen mode Exit fullscreen mode

Extras: Passing additional data to callbacks (without thisArg)

Because we’re using single-argument callbacks, pass extra parameters via closures or bind:

  • Closure factory:
const multiplyBy = (factor) => (n) => n * factor;
console.log([1,2,3].myMap(multiplyBy(10))); // [10,20,30]
Enter fullscreen mode Exit fullscreen mode
  • Bind fixed args (ignoring this):
function between(min, max, x) { return x >= min && x <= max; }
const inRange = between.bind(null, 3, 6);
console.log([1,3,5,7].myFilter(inRange)); // [3,5]
Enter fullscreen mode Exit fullscreen mode
  • Reduce with a cap via closure:
const cappedSum = (cap) => (acc, v) => Math.min(acc + v, cap);
console.log([2,5,10].myReduce(cappedSum(12), 0)); // 12
Enter fullscreen mode Exit fullscreen mode

Top comments (0)