# JavaScript Polyfills: Writing Array Methods From Scratch

Before we get into the code, there is something worth knowing about how JavaScript actually works in the browser. Every feature you use, from `forEach` to `Promise`, does not just exist out of thin air. There is a massive document called the ECMAScript Specification that lays out how JavaScript should behave. Browser engines like V8 (Chrome), SpiderMonkey (Firefox), and JavaScriptCore (Safari) read those specs and write the actual code that makes your browser understand JavaScript.

The problem is every browser team works at its own speed. When a new version of ECMAScript drops, some browsers implement the new features quickly, others take months. This gap between spec and implementation is where **polyfills** come in.

* * *

## What is a Polyfill?

A polyfill is a piece of code that gives older browsers a feature they do not natively support. The term was coined by Remy Sharp around 2009 and has been a part of web development since. Think of it like a compatibility patch: if your browser does not understand a method, the polyfill teaches it.

Every polyfill follows a specific pattern:

```js
if (!SomeObject.prototype.someMethod) {
  SomeObject.prototype.someMethod = function() {
    // custom implementation here
  };
}
```

The `if` check matters here. You only want to add the method if the browser does not already have it. You never want to override a working native implementation with your own code.

* * *

## Before You Write a Polyfill, Ask These Questions

Writing a polyfill is not just about copying behavior, it is about understanding it. Before writing one, work through these questions using the method as a reference. Let us use `Array.prototype.forEach` as the example:

**What does** `this` **refer to?** Whatever calls the method becomes `this`. You need to make sure `this` never becomes `null` or `undefined`, especially when you pass callbacks around.

**What if the callback is not a function?** `forEach` throws a `TypeError` if you pass it something that is not a function. Your polyfill should do the same. If someone passes the string `"hello"` as a callback, execution should stop and throw an error.

**Does it handle sparse arrays?** Arrays can have gaps: `[1, , 3]`. The empty slot at index 1 is not `undefined`, it just does not exist. `forEach` skips those slots and your polyfill should too.

**What does it return?** `forEach` always returns `undefined`. Other methods like `map` and `filter` return a new array. Know the return type before you write a single line.

**Does it mutate the original array?** `forEach` does not change the original array. Some methods do. Know which camp yours falls into.

These questions act as a blueprint. Once you answer them, writing the code becomes mechanical.

* * *

## Understanding Iterative Methods

The array methods covered here are called **iterative methods** because they loop over an array and run a callback function on each element. According to MDN, they all share a few common behaviors:

*   The callback receives `(element, index, array)` on every call
    
*   Empty slots in sparse arrays are skipped
    
*   The array length is read once at the start, so adding elements mid-loop does not extend the iteration
    
*   They accept an optional `thisArg` to control what `this` means inside the callback
    

Here is a quick overview of the methods and what they do:

| Method | Returns | Mutates original? | Usage |
| --- | --- | --- | --- |
| `forEach()` | `undefined` | No | You want to run side effects on each item |
| `map()` | New array | No | You want to transform each element |
| `filter()` | New array | No | You want to keep only matching elements |
| `find()` | First match or `undefined` | No | You want one specific element |
| `some()` | `true` or `false` | No | You want to check if any element matches |
| `every()` | `true` or `false` | No | You want to check if all elements match |
| `reduce()` | Single accumulated value | No | You want to combine all elements into one value |

* * *

## `forEach` Polyfill

`forEach` loops over every element and calls your callback with `(element, index, array)`. It never returns a value.

```js
if (!Array.prototype.myForEach) {
  Array.prototype.myForEach = function (callback, thisArg) {
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }

    if (this == null) {
      throw new TypeError("Cannot read properties of null or undefined");
    }

    const arr = this;
    const length = arr.length;

    for (let i = 0; i < length; i++) {
      if (i in arr) {
        callback.call(thisArg, arr[i], i, arr);
      }
    }

    return undefined;
  };
}
```

The method is renamed to `myForEach` so you can verify it works like the original without overriding it.

```js
// Test it
var fruits = ["apple", "banana", "cherry"];

fruits.myForEach(function (fruit, index) {
  console.log(index + ": " + fruit);
});
// Output:
// 0: apple
// 1: banana
// 2: cherry

// Sparse array test
const sparse = [1, , 3];
sparse.myForEach((val, i) => console.log(`index: ${i}, value: ${val}`));
// Output:
// index: 0, value: 1
// index: 2, value: 3
```

> **Why** `i in arr` **?** The `in` operator checks whether an index actually *exists* in the array, not just whether the value at that index is defined. A sparse array slot and `undefined` look the same when you read the value, but `in` tells you the difference. `for...of` iterates over values and would treat empty slots as `undefined`, which is wrong behavior for this polyfill.

* * *

## `map` Polyfill

`map` works like `forEach` with one key difference: it collects the return value of every callback and puts it into a brand new array. The original array stays untouched.

```js
if (!Array.prototype.myMap) {
  Array.prototype.myMap = function (callback, thisArg) {
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }

    if (this == null) {
      throw new TypeError("Array.prototype.myMap called on null or undefined");
    }

    const arr = this;
    const len = arr.length;
    const result = new Array(len); // same length as original

    for (let i = 0; i < len; i++) {
      if (i in arr) {
        result[i] = callback.call(thisArg, arr[i], i, arr);
      }
    }

    return result;
  };
}
```

```js
// Test it
const prices = [10, 25, 50, 100];
const withTax = prices.myMap(price => +(price * 0.5).toFixed(2));

console.log(withTax); // [ 5, 12.5, 25, 50 ]
console.log(prices);  // [10, 25, 50, 100] — unchanged

// Chaining works because myMap returns an array
const result = [1, 2, 3]
  .myMap(n => n * n)
  .myMap(n => n + 1);

console.log(result); // [ 2, 5, 10 ]
```

Notice that `result` is pre-allocated as `new Array(len)`. This preserves the sparse structure of the original array since empty slots remain empty in the result too.

* * *

## `filter` Polyfill

`filter` is similar to `map`, but instead of transforming each element, it only keeps elements where the callback returns a truthy value. One detail worth noting: it stores the original value, not the return value of the callback.

```js
if (!Array.prototype.myFilter) {
  Array.prototype.myFilter = function (callback, thisArg) {
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }

    if (this == null) {
      throw new TypeError("Array.prototype.myFilter called on null or undefined");
    }

    const arr = Object(this);       // handles edge cases like strings
    const len = arr.length >>> 0;  // coerce to unsigned 32-bit integer
    const result = [];

    for (let i = 0; i < len; i++) {
      if (i in arr) {
        if (callback.call(thisArg, arr[i], i, arr)) {
          result.push(arr[i]); // push the original value, not the callback result
        }
      }
    }

    return result;
  };
}
```

> **What does** `>>> 0` **do?** The `>>>` is the unsigned right shift operator. When used with `0`, it coerces `arr.length` to an unsigned 32-bit integer. This means if someone passes a weird length like `"10"` or `-1`, it gets converted to a valid number. It is a defensive technique you will see in production-grade polyfills.

```js
// Test it
var prices = [42, 88, 56, 91, 73, 34];

var budget = prices.myFilter(function(price) {
  return price <= 50;
});

console.log(budget);
// Output: [ 42, 34 ]
```

* * *

## `reduce` Polyfill

`reduce` is the most flexible of the bunch. It takes all the elements in an array and "reduces" them down to a single value, accumulating a result with each step.

The extra complexity here is the initial value. If you provide one, the loop starts at index 0 with your value as the accumulator. If you skip it, the first real element in the array becomes the accumulator and the loop starts at index 1.

```js
if (!Array.prototype.myReduce) {
  Array.prototype.myReduce = function (callback, initialValue) {
    if (typeof callback !== "function") {
      throw new TypeError(callback + " is not a function");
    }

    if (this == null) {
      throw new TypeError("Array.prototype.myReduce called on null or undefined");
    }

    const arr = this;
    const len = arr.length;

    if (len === 0 && arguments.length < 2) {
      throw new TypeError("Reduce of empty array with no initial value");
    }

    let accumulator;
    let startIndex;

    if (arguments.length >= 2) {
      accumulator = initialValue;
      startIndex = 0;
    } else {
      let initialFound = false;
      for (let i = 0; i < len; i++) {
        if (i in arr) {
          accumulator = arr[i];
          startIndex = i + 1;
          initialFound = true;
          break;
        }
      }

      if (!initialFound) {
        throw new TypeError("Reduce of empty array with no initial value");
      }
    }

    for (let i = startIndex; i < len; i++) {
      if (i in arr) {
        accumulator = callback(accumulator, arr[i], i, arr);
      }
    }

    return accumulator;
  };
}
```

```js
// Add up cart totals
const prices = [99, 49, 159.75, 39.28, 10.25];
const total = prices.myReduce((sum, price) => sum + price, 0);
console.log(total.toFixed(2));
// Output: 357.28

// Group employees by department
const employees = [
  { name: "John",  department: "HR" },
  { name: "Jane",  department: "Sales" },
  { name: "Harry", department: "HR" },
  { name: "June",  department: "Operations" },
];

const grouped = employees.myReduce((acc, item) => {
  if (!acc[item.department]) acc[item.department] = [];
  acc[item.department].push(item.name);
  return acc;
}, {});

console.log(grouped);
// Output: { HR: [ 'John', 'Harry' ], Sales: [ 'Jane' ], Operations: [ 'June' ] }
```

> **Always provide an initial value to** `reduce`**.** Skipping it on sparse or empty arrays leads to subtle bugs that are hard to track down. The extra argument costs nothing, and the safety it provides is worth it.

Here is the problematic version of the "no initial value" logic that a naive polyfill might use:

```js
// Dangerous — breaks on sparse arrays like [,,1,2,3]
accumulator = arr[0];
startIndex = 1;
```

The fix used in the polyfill above loops through the array to find the first *existing* index, which correctly handles sparse arrays.

* * *

## The Bigger Picture: When to Actually Use Polyfills

Writing polyfills is a great learning exercise, and they appear regularly in technical interviews. However, rolling your own polyfill in production code is not a good idea. Here is what to keep in mind:

**Always guard with a feature check.** Never override a native method. The `if (!SomeObject.prototype.someMethod)` check at the top of every polyfill exists for this reason.

**Use** `core-js` **or Babel in production.** These are battle-tested libraries that handle polyfilling correctly across hundreds of edge cases. [core-js](https://github.com/zloirock/core-js) is the industry standard. [Babel](https://babeljs.io/docs/babel-polyfill) can automatically inject polyfills based on your target browser list.

**Check browser support first.** Before adding a polyfill, visit [Can I Use](https://caniuse.com) and check if your target browsers already support the feature. Every polyfill you add increases the bundle size, so do not add them unnecessarily.

**Read the MDN docs for the method you are polyfilling.** The behavior section and edge case notes on MDN are the closest thing to a checklist for getting the implementation right. The [ECMAScript specification](https://tc39.es/ecma262/#sec-indexed-collections) goes even deeper if you need it.

* * *

## Conclusion

Polyfills exist because the web moves faster than browsers can keep up. The ECMAScript spec defines new features, but not every browser implements them at the same time. Understanding how to bridge that gap, whether by writing your own or using tools like Babel and core-js, is part of knowing how JavaScript actually works.

The four polyfills in this post cover the most common iterative array methods: `forEach`, `map`, `filter`, and `reduce`. Writing them yourself, even just once, teaches you things about `this`, sparse arrays, `arguments`, and prototype methods that reading documentation alone does not. The next time you use `[1,2,3].map(...)`, you will have a clearer picture of what is happening underneath.

* * *

## References

*   [MDN: Array - Iterative Methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#iterative_methods)
    
*   [core-js: The industry-standard polyfill library](https://github.com/zloirock/core-js)
    
*   [Babel Polyfill: How Babel handles polyfills](https://babeljs.io/docs/babel-polyfill)
    
*   [Can I Use: Browser compatibility tables](https://caniuse.com)
    
*   [ECMAScript Specification: Indexed Collections](https://tc39.es/ecma262/#sec-indexed-collections)
