Implement Array.prototype.filter
A filter polyfill walks the array, calls the predicate with the value, index and array, and collects the values it returns truthy for. Two details separate a good answer from a passing one: skip holes in sparse arrays, and honour the second thisArg parameter.
The question
Asked alongside map and reduce as a warm-up, then sharpened with a
follow-up. Almost everyone writes the loop correctly. Far fewer handle:
- The
thisArgsecond parameter, which the real method accepts. - Sparse arrays, where
delete arr[1]leaves a hole that must be skipped.
The solution
A plain indexed loop rather than for...of, because the index is needed both for the
callback and for the hole check.
Array.prototype.myFilter = function (callbackFn, thisArg) {
const result = [];
for (let index = 0; index < this.length; index += 1) {
// Skip holes: a sparse position is not a value to test.
if (!Object.prototype.hasOwnProperty.call(this, index)) continue;
const value = this[index];
if (callbackFn.call(thisArg, value, index, this)) {
result.push(value);
}
}
return result;
};
How it works
hasOwnProperty is what skips holes. A deleted position isn’t undefined,
it’s absent, and the real method never calls the predicate for one.
callbackFn.call(thisArg, ...) makes the second parameter work, and behaves like an
ordinary call when thisArg is undefined. The predicate gets value, index and source
array, matching the real signature, and the input is never touched.
The tests it has to pass
This is the part most explanations leave out. "It works" is not a claim you can check by reading. These are the 4 cases Practice Pad runs against your solution, and the implementation above passes all of them.
✓ keeps only matching values and skips empty positions
const values = [1, 0, 2, 3, 4];
delete values[1];
expectEqual(values.myFilter((value) => value > 2), [3, 4]);
✓ uses the supplied thisArg inside the callback
const selected = [1, 3, 4].myFilter(
function (value) {
return value > this.minimum;
},
{ minimum: 2 },
);
expectEqual(selected, [3, 4]);
✓ passes the original index to the callback and leaves the input unchanged
const letters = ["a", "b", "c", "d"];
expectEqual(letters.myFilter((value, index) => index % 2 === 0), ["a", "c"]);
expectEqual(letters, ["a", "b", "c", "d"]);
✓ returns a new array and leaves the original untouched
const values = [3, 1, 4];
const filtered = values.myFilter((value) => value > 1);
expectEqual(filtered, [3, 4]);
expectEqual(values, [3, 1, 4]);
expectEqual(filtered === values, false);
Edge cases to watch out for
Using for...of and losing the index
for...of gives you values but no index, so the predicate can’t receive its second argument and holes can’t be detected. An indexed loop is the right tool here.
Treating a hole as undefined
arr[1] on a hole evaluates to undefined, so a naive loop calls the predicate on it. The real method skips it entirely, which is why the first test deletes an index.
Ignoring thisArg
It’s easy to forget the second parameter exists. A predicate written as a regular function that reads this.minimum then fails, which is precisely the second easy test.
Using an arrow function for the prototype method
An arrow function has no this, so Array.prototype.myFilter = (fn) => {...} can’t see the array it was called on. Prototype methods must be regular functions.
Follow up questions
What is thisArg in Array.prototype.filter?
An optional second argument that sets the this value inside the callback. It only matters when the callback is a regular function that reads this; arrow functions ignore it entirely because they take this from where they were defined.
How does filter handle sparse arrays?
It skips holes without calling the callback for them, and the holes don’t appear in the result. Reading the index would give you undefined, which is why the check has to be hasOwnProperty rather than a comparison against undefined.
How is filter different from find and some?
All three walk the array with a predicate and differ in what they return and when they stop. filter visits everything and returns an array. find stops at the first match and returns that element. some stops at the first match and returns a boolean. Using filter(...).length > 0 where some would do walks the whole array for no reason.
Why can a prototype method not be an arrow function?
Arrow functions have no this binding of their own; they close over this from the surrounding scope, which at module level isn’t the array. A method that needs to know what it was called on has to be a regular function.
Practise this with the tests running
Practice Pad runs these 4 cases against your own solution, in a real editor, on your Mac. Twenty-four questions, and an optional AI panel that explains why a test failed.
Download for macOS Free. Requires macOS 12 or later on Apple Silicon.