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.

Easy to medium array iteration this binding prototype methods

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:

  1. The thisArg second parameter, which the real method accepts.
  2. 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.

Easy2 cases · holes and thisArg

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]);
Medium2 cases · indices and immutability

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.

See all 24 questions