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:
🧱 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:
🧠 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
🔥 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.