Have you ever wondered how the filter() works in JavaScript? I came accross that problem. After a few days of procrastination and fear, and reading the documentation and articles, I can say I finally understood how it works under the hood.
filter() takes an array and a test, a function that returns true or false for each item in the array. It returns a new array with the items that passed the test, leaving the original array unmodified.
Say you've got a list of users and you only want the active ones:
const users = [
{ name: "Amina", active: true },
{ name: "Brian", active: false },
{ name: "Carol", active: true },
];
const activeUsers = users.filter(user => user.active);
// [{ name: "Amina", active: true }, { name: "Carol", active: true }]
And just like magic, it works. You have your active users.
That is the whole mental model: loop, test, keep or skip, return a new array. filter() silently guarantees this until you try to build it yourself.
What happens with sparse arrays? What happens if you pass something that is not a function? What if your callback function relies on this?
My first version worked; it compiled, ran on a plain array, and it gave me exactly what I expected.
Array.prototype.myFilter = function (callbackFn, thisArg) {
const len = this.length;
let results = [];
let k = 0;
let newIndex = 0;
for (let k = 0; k < len; k++) {
if (Object.hasOwn(this, k)) {
let kValue = this[k];
const testPassed = callbackFn.call(thisArg, kValue, k, this);
if (testPassed) {
results[newIndex] = kValue;
newIndex = newIndex + 1;
}
}
}
return results;
}
Where my first attempt fell apart
No guard on the callback. I never checked if callbackFn was actually a plain function. The real filter() fails fast, at the top, with a message that tells you what actually went wrong.
Handling sparse arrays: Object.hasOwn worked, but it wasn't checking the same thing. Object.hasOwn(this, k) checks whether the this object has the k property; the only problem is that hasOwn checks only the object's own properties. It deliberately ignores the prototype chain.
After reading the ECMAScript documentation guide, this is the spec solution for how filter() works under the hood.
Array.prototype.myFilter = function (callbackFn, thisArg) {
// Check if the callback is a function; if not, throw a TypeError.
if (typeof callbackFn !== "function") {
throw new TypeError("This is not a function");
}
// This is the backbone of the filter function.
// 'this' refers to the value that called 'myFilter'.
// Object(this) ensures 'this' is always treated as an object.
const O = Object(this);
// Get the length of the array-like object.
// The `>>> 0` coercion forces the length into a sane, non-negative
// 32-bit unsigned integer, even if the real value is malformed.
const len = O.length >>> 0;
// Initialize an empty array that values passing the test will be appended to.
let result = [];
// Two separate indexes: k tracks position in the original array,
// N tracks position in the result array. Filter skips holes in sparse
// arrays, so without two separate counters, the result could end up
// with gaps instead of being densely packed.
let k = 0; // current index in the original array
let N = 0; // next index to write to in the result array
while (k < len) {
// Check if a property exists at index k. Using `in` (rather than
// Object.hasOwn) is deliberate — it also checks the prototype chain,
// matching the spec's HasProperty behavior exactly.
if (k in O) {
let kValue = O[k];
// Run the test, coercing the result to a real boolean.
const testPassed = Boolean(callbackFn.call(thisArg, kValue, k, O));
if (testPassed) {
result[N] = kValue;
N++;
}
}
k++; // always advance k, whether or not the value existed or passed
}
return result; // the new array containing only the elements that passed the test
};
What surprised me most wasn't any single fix; it was realizing how much invisible, deliberate design sits beneath a method I'd called a thousand times without ever wondering how it worked. Reading the spec isn't about memorizing algorithm steps to recite in an interview. It's about building the reflex to ask "but what if the input isn't clean?" before you ship something and consider it done. That reflex is worth more than any single polyfill you'll ever write.
For an in-depth explanation, please refer to this article by Priya Khanna
Top comments (0)