Array.prototype.map Polyfill
Implement Array.prototype.map from scratch. Master callback signatures, thisArg binding, and sparse-array hole preservation.
JavaScript · Arrays · Polyfills — Pratik Rai ·
Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.
Array.prototype.reduce folds an array into a single value by threading an accumulator through a callback. It is the most powerful of the array methods and the one with the most specification detail packed into it — most of which lives in a single question: what happens when you do not pass an initial value.
map and filter are variations on a loop. reduce has genuine branching logic in its setup, a four-parameter callback, and a documented error case. It is also the method people most often use incorrectly, because the no-initial-value form behaves in a way that is not obvious until you have been bitten by it.
reduce takes an optional second argument. That option changes three things at once:
With an initial value, the accumulator starts as that value and iteration begins at index 0. The callback runs once per element.
Without one, the accumulator starts as the first present element, and iteration begins after it. The callback runs one fewer time.
JSfile.javascript1[1, 2, 3].reduce((a, b) => a + b); // 6 — callback runs twice 2[1, 2, 3].reduce((a, b) => a + b, 0); // 6 — callback runs three times 3[1, 2, 3].reduce((a, b) => a + b, 10); // 16
And on an empty array with no initial value, it throws. This is the case that reaches production:
JSfile.javascript1[].reduce((a, b) => a + b); // TypeError: Reduce of empty array with no initial value 2[].reduce((a, b) => a + b, 0); // 0
Any reduce over data whose length you do not control needs an initial value. A filtered list that happens to be empty is enough to crash the page.
Note the phrase "first present element". On a sparse array the seed is the first index that actually exists, not index 0.
JSfile.javascript1Array.prototype.myReduce = function (callback, ...initialValue) { 2 if (this === null || this === undefined) { 3 throw new TypeError('Array.prototype.myReduce 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 12 let index = 0; 13 let accumulator; 14 15 if (initialValue.length > 0) { 16 accumulator = initialValue[0]; 17 } else { 18 // Seed from the first element that is actually present. 19 while (index < length && !(index in object)) index++; 20 if (index >= length) { 21 throw new TypeError('Reduce of empty array with no initial value'); 22 } 23 accumulator = object[index++]; 24 } 25 26 for (; index < length; index++) { 27 if (index in object) { 28 accumulator = callback(accumulator, object[index], index, object); 29 } 30 } 31 32 return accumulator; 33};
Collecting the initial value as a rest parameter is deliberate. Checking arguments.length > 1 works too, but a default parameter would be wrong — reduce(fn, undefined) explicitly passes undefined as the seed, and that must be distinguishable from passing nothing at all.
Also note reduce takes no thisArg. The fourth callback parameter is the array; there is no fifth slot for a context. That asymmetry with map and filter is a favourite interview detail.
Forgetting to return the accumulator. The most common reduce bug by a wide margin:
JSfile.javascript1items.reduce((acc, item) => { acc[item.id] = item; }, {}); // undefined 2items.reduce((acc, item) => { acc[item.id] = item; return acc; }, {}); // correct
The braced arrow returns undefined, which becomes the next accumulator, and the whole thing collapses on the second iteration.
Spreading inside the reducer is quadratic. {...acc, [k]: v} copies the entire accumulator on every element. Over a thousand items that is half a million property copies. Mutating a locally-owned accumulator and returning it is O(n) and perfectly safe — the object never escapes the reduce.
reduceRight is not reduce().reverse(). It walks right to left, which matters for anything non-commutative like function composition or string building.
"Implement map with reduce." Straightforward, and a good demonstration that reduce is the general case:
JSfile.javascript1const map = (arr, fn) => arr.reduce((acc, v, i) => (acc.push(fn(v, i, arr)), acc), []);
"Implement pipe or compose." The canonical use of reduce on an array of functions rather than data — usually the point where the concept clicks.
"When would you not use reduce?" A fair question with a real answer: when a plain loop reads better. A reduce that builds an object with a mutated accumulator is often less clear than a for...of doing the same thing. Reach for it when you are genuinely folding, not to avoid writing a loop.
reduce has no thisArg, unlike map and filter.Goal: Implement myReduce with correct handling of the missing-initialValue path and the empty-array error.
Continue learning with these related challenges
Implement Array.prototype.map from scratch. Master callback signatures, thisArg binding, and sparse-array hole preservation.
JavaScript · Arrays · Polyfills — Pratik Rai ·
Implement Array.prototype.filter from scratch. The key detail: push the original element, not the boolean result of the predicate.
JavaScript · Arrays · Polyfills — Pratik Rai ·
Group an array into buckets by a key function — now a real language feature as Object.groupBy.
JavaScript · ES6 — Pratik Rai ·
Implement Array.prototype.map from scratch. Master callback signatures, thisArg binding, and sparse-array hole preservation.
JavaScript · Arrays · Polyfills
Pratik Rai ·
Implement Array.prototype.filter from scratch. The key detail: push the original element, not the boolean result of the predicate.
JavaScript · Arrays · Polyfills
Pratik Rai ·
Group an array into buckets by a key function — now a real language feature as Object.groupBy.
JavaScript · ES6
Pratik Rai ·