Back to JavaScript
JavaScript
easy
mid

How would you implement a polyfill for Array.prototype.reduce?

Iterate the array applying the callback with an accumulator. If initial value supplied, start there; else use the first defined element as initial and start from index 1. Skip holes (sparse). Throw TypeError on empty array with no initial. Pass (acc, cur, i, arr) signature.

3 min read·~12 min to think through

Implementation

js
Array.prototype.myReduce = function (callback, initialValue) {
  if (this == null) throw new TypeError("array is null or undefined");
  if (typeof callback !== "function") throw new TypeError("callback is not a function");

  const arr = Object(this);
  const len = arr.length >>> 0;          // normalize to uint32
  let i = 0;
  let acc;

  if (arguments.length >= 2) {
    acc = initialValue;
  } else {
    // Find first defined index (skip holes in sparse arrays)
    while (i < len && !(i in arr)) i++;
    if (i >= len) throw new TypeError("Reduce of empty array with no initial value");
    acc = arr[i++];
  }

  for (; i < len; i++) {
    if (i in arr) {                       // skip holes
      acc = callback(acc, arr[i], i, arr);
    }
  }
  return acc;
};

Walk-through

  • this == null throws (called on null/undefined receiver).
  • Object(this) coerces in case someone calls on a primitive.
  • length >>> 0 is the spec way to normalize length to a 32-bit unsigned int.
  • With initial value: start accumulating from index 0.
  • Without: use the first present element as accumulator, start from the next index. Throw if no elements.
  • i in arr distinguishes holes from undefined — sparse arrays skip holes.
  • Callback receives (accumulator, currentValue, index, array).

Tests

js
[1, 2, 3, 4].myReduce((a, c) => a + c);        // 10 (no initial)
[1, 2, 3, 4].myReduce((a, c) => a + c, 100);   // 110 (with initial)
[].myReduce((a, c) => a + c);                  // TypeError
[].myReduce((a, c) => a + c, 0);               // 0
[1, , 3].myReduce((a, c) => a + c);            // 4 (skips hole)

Edge cases

  • Empty array, no initial → TypeError (matches spec).
  • Empty array, with initial → returns initial.
  • Sparse arraysforEach/reduce both skip holes; for..of doesn't.
  • Receiver coercion — called on array-like: Array.prototype.myReduce.call("abc", ...) works.

Why interviewers ask

Tests handling of: signature semantics, the no-initial-value branch, edge cases (empty + sparse), and proper error reporting. It's also a foundation for understanding reduceRight, transduce, etc.

Interview framing

"Initial value is optional — if provided, accumulate from index 0; if not, use the first present element and start from the next index, throwing on a completely empty array. Skip holes via i in arr (forEach and reduce both do this). Normalize length with length >>> 0 per spec. Callback signature is (acc, cur, i, arr). The empty-array-no-initial case throwing TypeError is the spec behavior most polyfills forget."

Follow-up questions

  • Implement reduceRight.
  • What's the difference between hole and undefined in an array?
  • How does length >>> 0 work?

Common mistakes

  • Forgetting the no-initial-value branch.
  • Not throwing on empty array without initial.
  • Treating undefined as a hole.

Performance considerations

  • O(n). Hole-check (`i in arr`) is slow on huge arrays — native reduce uses optimized internal iteration.

Edge cases

  • Empty + no initial → TypeError.
  • Sparse arrays.
  • Receiver is a string / array-like.

Real-world examples

  • Polyfill libraries (core-js), pre-IE9 environments, MDN reference impls.

Senior engineer discussion

Seniors handle the no-initial-value branch and sparse-array semantics, mention spec details (length coercion, TypeError messages), and identify when this is materially different from native reduce.

Related questions