You're totalling a list of transaction amounts to compute a running balance, or folding a list of words into a frequency map, or turning an array of { id, value } objects into a lookup by id. Each of these is the same shape: walk the array once, carry a result forward, return whatever ends up in your hand. That's reduce.
Array.prototype.reduce is a way of "reducing" elements in an array by calling a "reducer" callback function on each element of the array in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
Implement Array.prototype.reduce. To avoid overwriting the actual Array.prototype.reduce (which is being used by the autograder), implement it as Array.prototype.myReduce instead.
[1, 2, 3].myReduce((prev, curr) => prev + curr, 0); // 6
[1, 2, 3].myReduce((prev, curr) => prev + curr, 4); // 10
The reducer callback receives four arguments:
arr.myReduce(callback, initialValue?)
// callback(accumulator, currentValue, currentIndex, array)
There are several nuances to Array.prototype.reduce — read the MDN spec before attempting.
Highlights you must get right:
initialValue, non-empty array → the accumulator starts at this[0] and iteration begins at index 1.initialValue, empty array → throw a TypeError.initialValue provided, empty array → return the initialValue unchanged (callback never runs).[1, , 3] has a hole at index 1). myReduce must NOT invoke the callback for missing indices.(accumulator, currentValue, currentIndex, array). Pass this as the fourth argument.length at the start of the call — additions or deletions during iteration must not be picked up.You'll build Array.prototype.reduce from scratch. The base case is straightforward; the work is in handling the edge cases the spec calls out (missing initialValue, sparse arrays, empty arrays).
You've used .reduce to sum an array: [1, 2, 3].reduce((a, b) => a + b, 0) returns 6. Under the hood, that's a loop that walks the array left-to-right and folds each value into a running total. Your job is to write that loop yourself, name it myReduce, and hang it off Array.prototype — and to get the awkward parts right that the real reduce handles silently.
A reducer is just three things: an accumulator (the running result), a pointer that walks the array, and a callback that combines the accumulator with the value at the pointer. Step by step:
After the pointer reaches the end, the accumulator is your answer. Easy. The catch: the spec says different things about where the accumulator starts and which indices the pointer should visit.
Here's the simplest version, assuming initialValue is always provided and the array is dense:
Array.prototype.myReduce = function (callback, initialValue) {
let acc = initialValue;
for (let i = 0; i < this.length; i++) {
acc = callback(acc, this[i], i, this);
}
return acc;
};
This works for the happy path: [1,2,3].myReduce((a, b) => a + b, 0) returns 6. But three things will trip it:
initialValue — the real reduce lets you call [1,2,3].reduce((a, b) => a + b) without a seed, and starts the accumulator at this[0]. Our version sets acc = undefined and adds to it.initialValue — [].reduce(fn) throws TypeError. Ours silently returns undefined.[1, , 3] has a "hole" at index 1. The real reduce skips holes. Ours calls the callback with undefined.We need to handle each of these.
Array.prototype.myReduce = function (callback, initialValue) {
const len = this.length;
const hasInitial = arguments.length >= 2;
let acc;
let k = 0;
if (hasInitial) {
acc = initialValue;
} else {
// No seed — find the first present index to use as the starting acc.
while (k < len && !(k in this)) k++;
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
acc = this[k];
k++;
}
while (k < len) {
if (k in this) {
acc = callback(acc, this[k], k, this);
}
k++;
}
return acc;
};
module.exports = {};
Three details earn their lines:
arguments.length >= 2 — this is the only honest way to tell "caller didn't pass initialValue" from "caller passed undefined as initialValue". arr.myReduce(fn, undefined) is valid; initialValue === undefined would mis-classify it.k in this — checks whether index k is actually present. This is how you distinguish a hole from an explicit undefined.length at the top — the spec says reduce iterates over the original length; mutations to this during iteration don't extend or shrink the range.undefinedThis is the part most candidates miss. They look the same when printed, but they're different:
[1, , 3].length is 3. [1, undefined, 3].length is also 3. But 1 in [1, , 3] is false, while 1 in [1, undefined, 3] is true. The real reduce uses this distinction — sparse holes are skipped, explicit undefineds are visited.
[1, , 3].myReduce((a, b) => a + b) — no initialValue, sparse array.
hasInitial = false. Enter the "find first present index" loop.k = 0. 0 in this is true. Loop exits. acc = this[0] = 1. k++ → k = 1.k = 1. 1 in this is false (hole). Skip. k++ → k = 2.k = 2. 2 in this is true. acc = callback(1, 3, 2, this) = 4. k++ → k = 3.k >= len. Exit. Return 4.if (!initialValue) — falsy seeds (0, '', false, null) get treated as "no seed" and break. Use arguments.length >= 2.forEach / for..of — both have inconsistent hole semantics across engines. A plain indexed while loop with k in this is the only portable way.TypeError — silent undefined on [].myReduce(fn) looks fine until a caller does [].myReduce(fn) + 1 and gets NaN.The spec also defines:
reduceRight — same idea, walks the array right-to-left. About 6 lines of changes.Int32Array, etc.) — they have their own reduce that disallows missing initialValue on empty arrays the same way.(accumulator, currentValue, currentIndex, array). We pass this as the fourth arg so callers can reach back to the source array; some quick implementations skip this and break consumers that rely on it.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
You're totalling a list of transaction amounts to compute a running balance, or folding a list of words into a frequency map, or turning an array of { id, value } objects into a lookup by id. Each of these is the same shape: walk the array once, carry a result forward, return whatever ends up in your hand. That's reduce.
Array.prototype.reduce is a way of "reducing" elements in an array by calling a "reducer" callback function on each element of the array in order, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value.
Implement Array.prototype.reduce. To avoid overwriting the actual Array.prototype.reduce (which is being used by the autograder), implement it as Array.prototype.myReduce instead.
[1, 2, 3].myReduce((prev, curr) => prev + curr, 0); // 6
[1, 2, 3].myReduce((prev, curr) => prev + curr, 4); // 10
The reducer callback receives four arguments:
arr.myReduce(callback, initialValue?)
// callback(accumulator, currentValue, currentIndex, array)
There are several nuances to Array.prototype.reduce — read the MDN spec before attempting.
Highlights you must get right:
initialValue, non-empty array → the accumulator starts at this[0] and iteration begins at index 1.initialValue, empty array → throw a TypeError.initialValue provided, empty array → return the initialValue unchanged (callback never runs).[1, , 3] has a hole at index 1). myReduce must NOT invoke the callback for missing indices.(accumulator, currentValue, currentIndex, array). Pass this as the fourth argument.length at the start of the call — additions or deletions during iteration must not be picked up.You'll build Array.prototype.reduce from scratch. The base case is straightforward; the work is in handling the edge cases the spec calls out (missing initialValue, sparse arrays, empty arrays).
You've used .reduce to sum an array: [1, 2, 3].reduce((a, b) => a + b, 0) returns 6. Under the hood, that's a loop that walks the array left-to-right and folds each value into a running total. Your job is to write that loop yourself, name it myReduce, and hang it off Array.prototype — and to get the awkward parts right that the real reduce handles silently.
A reducer is just three things: an accumulator (the running result), a pointer that walks the array, and a callback that combines the accumulator with the value at the pointer. Step by step:
After the pointer reaches the end, the accumulator is your answer. Easy. The catch: the spec says different things about where the accumulator starts and which indices the pointer should visit.
Here's the simplest version, assuming initialValue is always provided and the array is dense:
Array.prototype.myReduce = function (callback, initialValue) {
let acc = initialValue;
for (let i = 0; i < this.length; i++) {
acc = callback(acc, this[i], i, this);
}
return acc;
};
This works for the happy path: [1,2,3].myReduce((a, b) => a + b, 0) returns 6. But three things will trip it:
initialValue — the real reduce lets you call [1,2,3].reduce((a, b) => a + b) without a seed, and starts the accumulator at this[0]. Our version sets acc = undefined and adds to it.initialValue — [].reduce(fn) throws TypeError. Ours silently returns undefined.[1, , 3] has a "hole" at index 1. The real reduce skips holes. Ours calls the callback with undefined.We need to handle each of these.
Array.prototype.myReduce = function (callback, initialValue) {
const len = this.length;
const hasInitial = arguments.length >= 2;
let acc;
let k = 0;
if (hasInitial) {
acc = initialValue;
} else {
// No seed — find the first present index to use as the starting acc.
while (k < len && !(k in this)) k++;
if (k >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
acc = this[k];
k++;
}
while (k < len) {
if (k in this) {
acc = callback(acc, this[k], k, this);
}
k++;
}
return acc;
};
module.exports = {};
Three details earn their lines:
arguments.length >= 2 — this is the only honest way to tell "caller didn't pass initialValue" from "caller passed undefined as initialValue". arr.myReduce(fn, undefined) is valid; initialValue === undefined would mis-classify it.k in this — checks whether index k is actually present. This is how you distinguish a hole from an explicit undefined.length at the top — the spec says reduce iterates over the original length; mutations to this during iteration don't extend or shrink the range.undefinedThis is the part most candidates miss. They look the same when printed, but they're different:
[1, , 3].length is 3. [1, undefined, 3].length is also 3. But 1 in [1, , 3] is false, while 1 in [1, undefined, 3] is true. The real reduce uses this distinction — sparse holes are skipped, explicit undefineds are visited.
[1, , 3].myReduce((a, b) => a + b) — no initialValue, sparse array.
hasInitial = false. Enter the "find first present index" loop.k = 0. 0 in this is true. Loop exits. acc = this[0] = 1. k++ → k = 1.k = 1. 1 in this is false (hole). Skip. k++ → k = 2.k = 2. 2 in this is true. acc = callback(1, 3, 2, this) = 4. k++ → k = 3.k >= len. Exit. Return 4.if (!initialValue) — falsy seeds (0, '', false, null) get treated as "no seed" and break. Use arguments.length >= 2.forEach / for..of — both have inconsistent hole semantics across engines. A plain indexed while loop with k in this is the only portable way.TypeError — silent undefined on [].myReduce(fn) looks fine until a caller does [].myReduce(fn) + 1 and gets NaN.The spec also defines:
reduceRight — same idea, walks the array right-to-left. About 6 lines of changes.Int32Array, etc.) — they have their own reduce that disallows missing initialValue on empty arrays the same way.(accumulator, currentValue, currentIndex, array). We pass this as the fourth arg so callers can reach back to the source array; some quick implementations skip this and break consumers that rely on it.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.