ESC

Type to search the knowledge base.

How to Approach Polyfill Questions

A repeatable method for interview polyfills: restate the spec, list edge cases, write a standalone function, then talk complexity and prototype mutation.

beginner6 min read
  • interview
  • javascript
  • polyfill

The usual polyfill miss is coding arr.map as a for loop that visits holes, forgets thisArg, and then slapping the function onto Array.prototype before anyone agreed to mutate a builtin. Interviewers are not grading whether you memorized a gist. They are grading whether you can restate the spec, list edges, and ship a standalone function you could actually test.

This page is a method. Worked implementations live on the linked polyfill pages. Use those after you can run this sequence without notes.

Key takeaways

  • Restate observable behavior from MDN / ECMA-262 before typing.
  • Write a standalone function first. Prototype assignment is an interview convention that mutates shared builtins.
  • Sparse holes, thisArg, empty reduce, and thenables are the scoring edges.
  • Complexity and “what I would not ship in production” belong in the last minute.

Spec, edges, standalone function, then prototype

Run this sequence every time, including on a prompt you have seen.

1. Restate the spec (60–90 seconds)

Say what the builtin does, not how you plan to loop.

Builtin family Observable contract (short)
map New array, same length; callback on present keys only; holes stay holes
filter Compact array of values whose callback is truthy; skips holes
reduce Throws on empty array with no initial value; skips holes
Promise.all Thenables via Promise.resolve; reject on first rejection; fulfill in input order
Promise.allSettled Never rejects for input rejections; {status, value|reason}
Promise.race Settles with the first settlement, including rejection
Promise.any Fulfills on first fulfill; all-reject is AggregateError
Function.prototype.bind Fixes this, prepends args; new on the bound function uses the original prototype
Object.create New object whose [[Prototype]] is the argument; null proto is allowed

If you cannot restate it, ask: “Should this match Array.prototype.map on sparse arrays?” Interviewers usually say yes.

Primary text: ECMA-262, MDN JavaScript reference.

2. List edges out loud (30 seconds)

Keep a standing checklist. Tick the ones that apply.

  1. null / undefined this → TypeError (array extras)
  2. Array-likes ({ 0: 'a', length: 1 }), not only Array.isArray
  3. Holes vs undefined (i in O is the test map uses)
  4. thisArg for the callback
  5. Callback that mutates the source
  6. Thenables vs values (Promise.resolve wraps both)
  7. Empty input (reduce without init throws; all([]) fulfills [])
  8. Bound functions called with new

Skipping holes is the most common fail. for (let i = 0; i < len; i++) result.push(fn(arr[i])) is not map. It turns holes into undefined.

3. Standalone function, then (maybe) prototype

Write a function you can unit test. Do not start with Array.prototype.myMap = .... Mutating builtins leaks across tests and can break for...in. If the interviewer insists on Array.prototype.map, say you are doing it as interview convention and keep the body identical.

Worked example: a map that matches hole behavior.

function mapStandalone(arrayLike, callback, thisArg) {
  if (arrayLike == null) {
    throw new TypeError('mapStandalone called on null or undefined');
  }
  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  const O = Object(arrayLike);
  const len = O.length >>> 0;
  const result = new Array(len);

  for (let i = 0; i < len; i++) {
    if (i in O) {
      result[i] = callback.call(thisArg, O[i], i, O);
    }
  }
  return result;
}

const sparse = [, 2, , 4];
const doubled = mapStandalone(sparse, (x) => x * 2);
// doubled.length === 4
// 0 in doubled === false  (hole preserved)
// doubled[1] === 4

length >>> 0 is ToUint32, the same length conversion array extras use. i in O is false for holes, true for an own undefined. That distinction is the interview.

If they want the prototype form:

// Interview convention only: mutates a shared builtin.
if (typeof Array.prototype.map !== 'function') {
  Array.prototype.map = function map(callback, thisArg) {
    return mapStandalone(this, callback, thisArg);
  };
}

Prefer if (!Array.prototype.map) in a real polyfill so you do not overwrite a native. In interviews they often want myMap to avoid colliding.

Full walkthroughs: array map polyfill, array filter, array reduce, array flat.

4. Promise-shaped polyfills

Same method, different edges. Always lift inputs with Promise.resolve so a thenable counts as a promise. Promise.all must preserve index order, not completion order. It still rejects on the first rejection and does not wait for the rest to settle (those work may still run).

function allStandalone(iterable) {
  const items = [...iterable];
  if (items.length === 0) return Promise.resolve([]);

  return new Promise((resolve, reject) => {
    const out = new Array(items.length);
    let remaining = items.length;
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        out[i] = value;
        remaining -= 1;
        if (remaining === 0) resolve(out);
      }, reject);
    });
  });
}

That is the skeleton, not a complete spec polyfill (this of Promise.resolve, iterator errors). Use it to talk thenables and order, then point at Promise.all polyfill, allSettled, race, any, finally.

5. Complexity, then production notes (last minute)

Family Time Extra space
map / filter O(n) callback calls on present keys O(n) result
reduce O(n) O(1) besides accumulator
Promise.all O(n) attaches O(n) result
bind O(1) per call + prepended args one bound function

Production notes interviewers like:

Footguns

  • Using forEach inside a map polyfill (forEach also skips holes, but you still need a same-length result)
  • reduce with no init on [,,] (all holes): treat like empty, throw
  • Promise.all with out.push on settle (wrong order)
  • Assigning to Array.prototype in a shared editor, then wondering why the next question’s tests fail
  • Implementing flat with typeof x === 'object' instead of Array.isArray (plain objects are not nested arrays)

Interview out-loud answer

“I would match Array.prototype.map: coerce the receiver, read length as a uint32, skip missing indexes with in, and write a same-length array so holes stay holes. Callback gets value, index, and the object, plus optional thisArg. I would keep it as a standalone function unless you want it on the prototype, which mutates a builtin. Time is O(n) over length, with the callback skipped on holes. I would not ship a hand-rolled map in production if the native exists.”

Further reading