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.
- 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, emptyreduce, 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.
null/undefinedthis →TypeError(array extras)- Array-likes (
{ 0: 'a', length: 1 }), not onlyArray.isArray - Holes vs
undefined(i in Ois the testmapuses) thisArgfor the callback- Callback that mutates the source
- Thenables vs values (
Promise.resolvewraps both) - Empty input (
reducewithout init throws;all([])fulfills[]) - 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:
- Feature-detect; do not clobber natives
- Array extras are generic on purpose (array-likes, strings)
- Shipping
core-jsis how apps actually polyfill. Interview code is a teaching tool. See polyfills and baseline targets bindpolyfills that ignoreneware incomplete (call, apply, and bind, new operator)
Footguns
- Using
forEachinside amappolyfill (forEachalso skips holes, but you still need a same-length result) reducewith no init on[,,](all holes): treat like empty, throwPromise.allwithout.pushon settle (wrong order)- Assigning to
Array.prototypein a shared editor, then wondering why the next question’s tests fail - Implementing
flatwithtypeof x === 'object'instead ofArray.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 within, 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.”