Implement Array.prototype.map, filter, and reduce
The Short Answer
map, filter, and reduce are higher-order array methods that each take a callback and walk the array once. map transforms every element into a new array of the same length, filter keeps only the elements whose callback returns truthy, and reduce folds the whole array down to a single accumulated value. Writing them by hand proves you understand callbacks, the standard (element, index, array) signature, and immutability.
The reference below shows the native behaviour for all three. Keep it as the target — our polyfills must produce identical results, including passing the index and the source array to the callback.
const numbers = [1, 2, 3, 4];
numbers.map((value) => value * 2); // [2, 4, 6, 8]
numbers.filter((value) => value % 2 === 0); // [2, 4]
numbers.reduce((sum, value) => sum + value, 0); // 10
The Shared Callback Signature
All three methods invoke their callback with the same three arguments. Forgetting the second and third is the most common way a homemade polyfill diverges from the native one, so it is worth stating the contract explicitly before we implement anything.
Callback receives
- The current element being processed
- The index of that element
- The whole array the method was called on
Implementing map
map builds a brand-new array without touching the original. Inside the method this is the array, so we loop over its indices, call the callback with the full signature, and push each transformed value. The result always has the same length as the source.
Array.prototype.myMap = function (callback) {
const result = [];
for (let index = 0; index < this.length; index++) {
// pass element, index, and the source array
result.push(callback(this[index], index, this));
}
return result; // new array, original untouched
};
[1, 2, 3].myMap((value) => value * 2); // [2, 4, 6]
map never mutates
The original array is read-only here — we only push into a fresh result. That immutability is the whole point of map and why it pairs so well with React state updates.
Implementing filter
filter is almost identical to map, but instead of transforming we test. We push the element only when the callback returns a truthy value, so the result is a subset of the original with the same elements in the same order. The length can be anywhere from zero to the full array.
Array.prototype.myFilter = function (callback) {
const result = [];
for (let index = 0; index < this.length; index++) {
// keep the element only if the predicate is truthy
if (callback(this[index], index, this)) {
result.push(this[index]);
}
}
return result;
};
[1, 2, 3, 4].myFilter((value) => value % 2 === 0); // [2, 4]
Implementing reduce
reduce is the trickiest of the three because of the optional initial value. If an initial value is supplied, the accumulator starts there and we iterate from index 0. If it is omitted, the accumulator starts as the first element and iteration begins at index 1. We also must throw on an empty array with no initial value, exactly like the native method. Trace how the starting index shifts based on whether the initial value exists.
Array.prototype.myReduce = function (callback, initialValue) {
const hasInitial = arguments.length >= 2;
if (this.length === 0 && !hasInitial) {
throw new TypeError("Reduce of empty array with no initial value");
}
// accumulator + starting index depend on whether initialValue was given
let accumulator = hasInitial ? initialValue : this[0];
let startIndex = hasInitial ? 0 : 1;
for (let index = startIndex; index < this.length; index++) {
accumulator = callback(accumulator, this[index], index, this);
}
return accumulator;
};
[1, 2, 3, 4].myReduce((sum, value) => sum + value, 0); // 10
[1, 2, 3, 4].myReduce((sum, value) => sum + value); // 10 (starts at index 1)
Why arguments.length, not a truthy check
We test arguments.length >= 2 rather than if (initialValue) because a legitimate initial value of 0, '', or null is falsy. Checking how many arguments were actually passed is the only reliable way to know if an initial value was supplied.
Common Mistakes
Treating a falsy initial value as missing
Using `if (initialValue)` to detect the initial value breaks reduce whenever the seed is 0, empty string, or null — all valid starting values.
✅Check `arguments.length >= 2` to know whether an initial value was actually passed.
Dropping index and array from the callback
Calling `callback(this[index])` only passes the element, so callbacks that rely on the index or source array silently misbehave.
✅Always invoke the callback with the full `(element, index, this)` signature.
Why Interviewers Ask This
These three methods are the backbone of functional data transformation in JavaScript and React. Reimplementing them shows you understand higher-order functions, the exact callback contract, and why map and filter return new arrays while reduce collapses to one value. The reduce edge cases — optional initial value and the empty-array throw — are where interviewers separate surface knowledge from real fluency.
Quick Revision Cheat Sheet
map: Transforms each element → new array of equal length
filter: Keeps elements where the predicate is truthy → subset
reduce: Folds the array into a single accumulated value
Callback args: (element, index, array) for all three
Immutability: map and filter never mutate the original array
reduce seed: Detect the initial value via arguments.length >= 2