Building a filter Polyfill in JavaScript (From First Principles)

Building a filter Polyfill in JavaScript (From First Principles)

Modern JavaScript gives us powerful array utilities like Array.prototype.filter. But understanding how they work internally is where you actually level up.

Let’s implement a polyfill for filter—step by step.


🧠 What does filter do?

At a high level:

Take an array → keep elements that pass a condition → return a new array        

Example:

const arr = [1, 2, 3, 4];

const even = arr.filter(num => num % 2 === 0);

console.log(even); // [2, 4]        

🔍 Behavior Contract (Important)

Before coding, define what filter guarantees:

  1. Iterates over the array
  2. Calls callback with:
  3. Includes element if callback returns truthy
  4. Returns a new array
  5. Does not mutate original array


🧱 Step 1: Basic Implementation

Let’s write the simplest version:

Array.prototype.myFilter = function (callback) {
  const result = [];

  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) {
      result.push(this[i]);
    }
  }

  return result;
};        

⚠️ Problem with this version

It works for simple cases, but it’s not spec-compliant.

Missing:

  • thisArg support
  • error handling
  • sparse array handling


🧠 Step 2: Add thisArg

Native filter allows binding context:

arr.filter(callback, thisArg)        

Fix:

Array.prototype.myFilter = function (callback, thisArg) {
  const result = [];

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

  return result;
};        

⚠️ Step 3: Handle Edge Cases

1. this should not be null/undefined

if (this == null) {
  throw new TypeError("Cannot read property 'filter' of null");
}        

2. Callback must be a function

if (typeof callback !== "function") {
  throw new TypeError(callback + " is not a function");
}        

3. Skip sparse array holes

if (!(i in arr)) continue;        

🔥 Final Polyfill (Production-grade)

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

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const result = [];
  const arr = Object(this);

  for (let i = 0; i < arr.length; i++) {
    if (!(i in arr)) continue; // skip empty slots

    if (callback.call(thisArg, arr[i], i, arr)) {
      result.push(arr[i]);
    }
  }

  return result;
};        

🧪 Test Cases

const arr = [1, 2, 3, 4];

console.log(arr.myFilter(x => x > 2)); 
// [3, 4]        

Sparse array

const arr = [1, , 3];

arr.myFilter(x => true);
// [1, 3] ✅ skips empty slot        

thisArg

const obj = { limit: 2 };

[1,2,3].myFilter(function(x) {
  return x > this.limit;
}, obj);

// [3]        

🧠 Key Concepts You Learn

  • this binding
  • call vs direct invocation
  • sparse arrays
  • defensive programming
  • how native APIs behave internally


🔥 Common Mistakes

❌ Forgetting to return new array
❌ Not handling `thisArg`
❌ Ignoring sparse arrays
❌ Mutating original array        

⚡ Mental Model

filter = iterate → test → keep → return new array        

🚀 Final Insight

Writing polyfills isn’t about memorizing code—it’s about understanding:

“How JavaScript APIs are designed internally”        

Once you get this, map, reduce, some, every all become trivial.

To view or add a comment, sign in

Explore content categories