#Arrays#Polyfill

Array.prototype.filter Polyfill

Implement Array.prototype.filter from scratch. The key detail: push the original element, not the boolean result of the predicate.

By Pratik RaiEasy

Array.prototype.filter returns a new array containing only the elements whose callback returned a truthy value. It shares most of its machinery with map, but differs in one structural way that trips people up: the output indices do not line up with the input indices.

Why implement it yourself

filter is where the difference between "returns the right values" and "matches the specification" becomes visible. The callback still receives the original index even though the result array is being packed densely, holes are still skipped, and the truthiness rules catch people who expected strict booleans.

The index mismatch

This is the detail that separates filter from map:

JSfile.javascript
1['a', 'b', 'c', 'd'].filter((value, index) => { 2 console.log(value, index); // 0, 1, 2, 3 — the source indices 3 return index % 2 === 0; 4}); 5// ['a', 'c'] — now at indices 0 and 1

The callback always sees where the element came from, never where it will end up. So the implementation needs two counters: one walking the source, one tracking the write position in the result.

The implementation

JSfile.javascript
1Array.prototype.myFilter = function (callback, thisArg) { 2 if (this === null || this === undefined) { 3 throw new TypeError('Array.prototype.myFilter called on null or undefined'); 4 } 5 if (typeof callback !== 'function') { 6 throw new TypeError(`${callback} is not a function`); 7 } 8 9 const object = Object(this); 10 const length = object.length >>> 0; 11 const result = []; 12 13 for (let i = 0; i < length; i++) { 14 if (i in object) { 15 const value = object[i]; 16 if (callback.call(thisArg, value, i, object)) { 17 result.push(value); 18 } 19 } 20 } 21 22 return result; 23};

Note that unlike map, push is correct here. The result is always dense — filter never produces holes, even when the input has them. Holes are simply skipped, never tested, and never carried through.

What the specification requires

length is read once. Elements appended by the callback are not visited. Elements deleted before they are reached are not visited either, since the i in object check fails by then.

The return value is coerced to boolean. Any truthy value keeps the element. This is why [0, 1, 2, ''].filter(Boolean) is a common idiom for stripping falsy values — Boolean is just a function returning a truthy or falsy result.

It is generic. Like map, filter works on array-likes via call. Array.prototype.filter.call(document.querySelectorAll('div'), fn) was standard practice before Array.from existed.

thisArg binds the callback's this, and arrow functions ignore it.

Edge cases worth knowing

Returning nothing is returning undefined. A callback with a missing return filters everything out, because undefined is falsy. With a braced arrow function this is easy to do by accident:

JSfile.javascript
1items.filter(x => { x.active }); // always [] — no return 2items.filter(x => x.active); // correct

filter does not mutate. It always allocates a new array, even when nothing is removed. Chaining .filter().map().filter() over a large array allocates an intermediate at every step — usually irrelevant, occasionally the reason a hot loop is slow.

Filtering while iterating the same array is safe; filtering while mutating it is not. Deleting elements from the source inside the callback shifts subsequent indices, and the captured length no longer describes reality. If you need that, iterate backwards over a copy.

filter(Boolean) and TypeScript. The idiom works at runtime but does not narrow types on its own — you need a type predicate like (x): x is Foo => Boolean(x) for the compiler to drop null from the resulting element type.

Common follow-ups

"Implement filter using reduce." A neat one-liner, and a good check that you understand the accumulator:

JSfile.javascript
1const myFilter = (arr, fn) => 2 arr.reduce((acc, value, i) => (fn(value, i, arr) ? [...acc, value] : acc), []);

Worth flagging in the same breath that spreading inside a reducer is quadratic — acc.push(value); return acc; is the version you would actually ship.

"How would you filter and transform in one pass?" flatMap with an empty array as the reject signal, or a plain reduce. This matters when the source is large enough that two passes cost real time.

Key takeaways

  • The callback receives the source index, not the destination index.
  • Holes are skipped and never appear in the output; the result is always dense.
  • Read length once and coerce with >>> 0.
  • The callback's return value is judged by truthiness, so a missing return empties the array.
  • filter allocates a new array every time and never mutates the original.

Goal: Implement myFilter correctly. Focus on pushing the original element, not the predicate result.

Frequently asked questions

What does Array.prototype.filter guarantee?
It returns a new array containing the elements for which the callback returned a truthy value. It never mutates the original, and the callback receives `(element, index, array)` — all three, even though most code only uses the first.
What is the mistake people make when implementing it?
Pushing the boolean instead of the element. `result.push(callback(...))` compiles, runs, and returns an array of `true` and `false`, which is why interviewers watch this line specifically. The predicate decides *whether* to keep the value; the value that gets kept is the original element.
How should the implementation handle sparse arrays?
Skip holes with an `i in this` check. `[1, , 3]` has no index 1, and `filter` is specified to leave holes alone rather than calling the callback with `undefined`. Testing `this[i] !== undefined` is the common substitute and it is wrong — it also skips real `undefined` values somebody deliberately stored.
What follow-ups come after filter?
Implementing `map` and `reduce`, then being asked to write `filter` in terms of `reduce`. That last one is a check on whether you see the three as the same traversal with different accumulation, rather than three unrelated methods.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

Array.prototype.map Polyfill

Implement Array.prototype.map from scratch. Master callback signatures, thisArg binding, and sparse-array hole preservation.

JavaScript · Arrays · PolyfillsPratik Rai ·

JavaScript

Array.prototype.flat Polyfill

Implement Array.prototype.flat from scratch with a configurable depth. Default depth is 1, not Infinity.

JavaScript · Arrays · RecursionPratik Rai ·

JavaScript

Array.prototype.reduce Polyfill

Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.

JavaScript · Arrays · PolyfillsPratik Rai ·