On this page
Polyfill: map, filter and reducehigh-yield
Last reviewed 22 Sept 2026
Problem
Implement myMap, myFilter and myReduce on Array.prototype, matching the built-ins:
[1, 2, 3].myMap((x) => x * 2); // [2, 4, 6][1, 2, 3, 4].myFilter((x) => x % 2 === 0); // [2, 4][1, 2, 3].myReduce((sum, x) => sum + x, 0); // 6[1, 2, 3].myReduce((sum, x) => sum + x); // 6 (no initial value)Clarifying questions
- Does the callback receive
(element, index, array)? Yes, all three. - Should
mapandfilteraccept athisArg? Yes, the built-ins do. - Should holes in sparse arrays (
[1, , 3]) be skipped? Yes — the built-ins skip them. - What should
reducedo on an empty array with no initial value? Throw aTypeError.
Approach
Loop over the indices, skip holes with i in this, call the callback, and collect results. reduce needs one extra decision: if no initial value was passed, the first existing element becomes the accumulator and the loop starts after it. Check arguments.length rather than initial === undefined, because undefined is a legitimate initial value.
Step-by-step build
Step 1 — myMap
Array.prototype.myMap = function (cb, thisArg) { const out = new Array(this.length); for (let i = 0; i < this.length; i++) { if (i in this) out[i] = cb.call(thisArg, this[i], i, this); } return out;};Step 2 — myFilter
Array.prototype.myFilter = function (cb, thisArg) { const out = []; for (let i = 0; i < this.length; i++) { if (i in this && cb.call(thisArg, this[i], i, this)) out.push(this[i]); } return out;};Step 3 — myReduce
Array.prototype.myReduce = function (cb, initial) { let i = 0; let acc = initial; if (arguments.length < 2) { while (i < this.length && !(i in this)) i++; // find the first real element if (i >= this.length) throw new TypeError('Reduce of empty array with no initial value'); acc = this[i++]; } for (; i < this.length; i++) { if (i in this) acc = cb(acc, this[i], i, this); } return acc;};Step 4 — validate the callback
if (typeof cb !== 'function') throw new TypeError(cb + ' is not a function');Final code
Array.prototype.myMap = function (cb, thisArg) { if (typeof cb !== 'function') throw new TypeError(cb + ' is not a function'); const len = this.length; const out = new Array(len); for (let i = 0; i < len; i++) { if (i in this) out[i] = cb.call(thisArg, this[i], i, this); } return out;};
Array.prototype.myFilter = function (cb, thisArg) { if (typeof cb !== 'function') throw new TypeError(cb + ' is not a function'); const len = this.length; const out = []; for (let i = 0; i < len; i++) { if (i in this && cb.call(thisArg, this[i], i, this)) out.push(this[i]); } return out;};
Array.prototype.myReduce = function (cb, initial) { if (typeof cb !== 'function') throw new TypeError(cb + ' is not a function'); const len = this.length; let i = 0; let acc = initial; if (arguments.length < 2) { while (i < len && !(i in this)) i++; if (i >= len) throw new TypeError('Reduce of empty array with no initial value'); acc = this[i++]; } for (; i < len; i++) { if (i in this) acc = cb(acc, this[i], i, this); } return acc;};Edge cases
- Sparse arrays:
[1, , 3].myMap(f)keeps the hole and never callsffor index 1. reduceon[]with no initial value throws; with an initial value it returns it untouched.reducewithundefinedas the explicit initial value must use it — hencearguments.length.- The callback pushes to the array while looping: the built-ins read
lengthonce at the start, so elements added later are not visited. Cachinglengives the same behaviour.
Follow-ups
- Implement
reduceRight. Same logic, walking fromlen − 1down. - Write
mapandfilterusing only yourreduce. A common twist to check you understand the accumulator. - Why not use arrow functions for the polyfills?
thismust be the array the method is called on.
Common mistakes
if (!initial)instead of checking whether it was passed — breaksreduce(fn, 0).- Using
pushinmap, which collapses holes and shifts indices. - Forgetting the
indexandarrayarguments to the callback. - Ignoring
thisArg.
Related
- Concepts: reduce polyfill in the JavaScript track.
- Next: Array.flat.