String.prototype.trim
beginnerStrip whitespace without regex catastrophes — and enumerate what counts as whitespace.
Implement Array.prototype.reduce in JavaScript, including the no-initial-value rule, the empty-array TypeError, and the arguments.length trick most candidates miss.
Implement
Array.prototype.myReduce(callback, initialValue)matching the built-in — including what happens wheninitialValueis omitted, when it's explicitlyundefined, and when the array is empty.
The loop is trivial; the question lives entirely in the initial-value rules. There are three distinct cases and most candidates conflate two of them:
[1, 2, 3].reduce(fn); // no initial value → acc starts as 1, loop starts at index 1
[1, 2, 3].reduce(fn, undefined); // initial value IS undefined → acc starts as undefined, index 0
[].reduce(fn); // no initial value + empty array → TypeError
[].reduce(fn, 0); // → 0, callback never runsCase 2 is the planted trap: you cannot detect "omitted" by checking initialValue === undefined — you must check arguments.length.
reduce is the universal fold — every aggregation is a special case of it: summing, groupBy (now Object.groupBy), converting arrays to lookup maps, composing functions in pipe, and running promises in series. Interviewers like it because implementing it proves you understand the accumulator contract you use daily.
Array.prototype.myReduce = function (callback, initialValue) {
if (this == null) {
throw new TypeError("Array.prototype.myReduce called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError(`${callback} is not a function`);
}
const arr = Object(this);
const length = arr.length >>> 0; // ToUint32, handles array-likes
// arguments.length — NOT a === undefined check — detects omission.
// reduce(fn, undefined) legitimately starts with acc = undefined.
const hasInitial = arguments.length >= 2;
let accumulator = initialValue;
let index = 0;
if (!hasInitial) {
// Skip holes while searching for the first real element
while (index < length && !(index in arr)) {
index++;
}
if (index >= length) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = arr[index++]; // first element becomes acc; loop starts after it
}
for (; index < length; index++) {
if (index in arr) { // native reduce skips holes in sparse arrays
accumulator = callback(accumulator, arr[index], index, arr);
}
}
return accumulator;
};[5, 10, 20].myReduce((acc, x) => acc + x);
// hasInitial = false (arguments.length === 1)
// index 0 exists → accumulator = 5, index = 1
// i=1: acc = 5 + 10 = 15
// i=2: acc = 15 + 20 = 35
// → 35 (callback ran TWICE for a 3-element array — a classic quiz point)
[5, 10, 20].myReduce((acc, x) => acc + x, 100);
// hasInitial = true → acc = 100, callback runs 3 times → 135reduce(fn, undefined) vs reduce(fn) — the arguments.length trick. If your implementation treats them the same, [].reduce(fn, undefined) incorrectly throws (native returns undefined) and [false, 1].reduce(...)-style falsy-first-element logic goes wrong in subtler variants.TypeError: Reduce of empty array with no initial value. Your implementation should throw, not return undefined.[1, , 3].reduce((a, b) => a + b) is 4, not NaN: holes are skipped, not visited as undefined. The index in arr guard reproduces this. (Contrast: [1, undefined, 3] visits the undefined.)(accumulator, currentValue, index, array). When there's no initial value, the first callback invocation gets index === 1, not 0.length once up front; elements appended during the loop are not visited. Mentioning this shows you read specs, not blog posts.if (initialValue === undefined) — the single most common bug in this question, and it's exactly what the interviewer planted.undefined for the empty-no-initial case instead of throwing.index and array to the callback — real code (e.g. building index maps) relies on them.reduce(arr, fn) when asked for the prototype method, then mishandling this.let calls = 0;
const result = [1, 2, 3].reduce((acc, x) => {
calls++;
return acc + x;
});
console.log(result, calls);| Direction | When | |
|---|---|---|
reduce | left → right | folds, aggregations, pipe |
reduceRight | right → left | compose, right-associative folds |
for...of + accumulator variable | either | when the reducer body has early exits or is getting unreadable |
Strong candidates volunteer that reduce has no break — if you need early termination, a loop (or some/every) is the honest tool, and "clever" reduce chains that allocate a new object per iteration ({ ...acc, [k]: v } — O(n²)) are a known performance smell in review.
map and filter using your myReduce." — map: push fn(x) into the accumulator array; filter: push conditionally. Standard warm-down proving the fold is universal.forEach/map hole semantics predating ES5; also why Array(3).reduce(fn) throws (all holes → no first element).reduce<T>(fn: (acc: T, v: T, ...) => T): T and reduce<U>(fn: (acc: U, v: T, ...) => U, init: U): U. The two-overload shape is the arguments.length rule expressed in types.reduce in code review?" — accumulator spreads per iteration, side-effect-only reducers (use forEach), or anything a named loop states more clearly.Strip whitespace without regex catastrophes — and enumerate what counts as whitespace.
Write a small recursive-descent parser — the deepest polyfill question in the set.
Split source text into tokens — a warm-up for parsing questions like JSON.parse.
Serialize values by hand: undefined, functions, cycles, and all the special cases JSON defines.