#Polyfill: Array.prototype.map
My "head teaser" from the notes. The details interviewers look for: respect thisArg, pass (value, index, array), and skip holes in sparse arrays.
A polyfill is your own implementation of a built-in, written so older environments get the feature (core-js is a library of them). In an interview it's a test of whether you know the built-in's contract rather than just its happy path: what this is inside a method on Array.prototype, which arguments the callback receives, what happens with an optional second argument, and how gaps in an array are treated. The mental model: map is a loop that builds a new array of the same length, slot by slot, where each slot holds whatever the callback returned for the matching input slot.
Array.prototype.myMap = function (callbackFn, thisArg) {
if (typeof callbackFn !== "function") {
throw new TypeError(`${callbackFn} is not a function`);
}
const len = this.length;
const result = new Array(len);
for (let k = 0; k < len; k++) {
// Skip holes in sparse arrays, like the real map does
if (Object.hasOwn(this, k)) {
result[k] = callbackFn.call(thisArg, this[k], k, this);
}
}
return result;
};
[1, 2, , 4].myMap((i) => i * i); // [1, 4, <empty>, 16]- Adding the function to
Array.prototypemakes it available on every array. When you call[1, 2, , 4].myMap(fn),thisinside it is that array — which only works because it's a regularfunction. - The type check runs first, so
[1].myMap(5)throwsTypeError: 5 is not a functionbefore doing any work, just like the built-in. lenis read once, up front —4here. If the callback pushes onto the array, the new elements aren't visited, matching the realmap.new Array(4)creates a result with length4but no elements yet: four holes.- The loop runs
k = 0…3.Object.hasOwn(this, k)asks "does this array actually have an element at indexk?". Index2was written as, ,— a hole, notundefined— so the check isfalseandresult[2]is left as a hole. - For the other indexes it calls
callbackFn.call(thisArg, this[k], k, this): value, index and the whole array, withthisinside the callback set tothisArg.resultfills in as[1],[1, 4], (skip),[1, 4, <empty>, 16]. - It returns a new array; the original is untouched. Because it only uses
this.lengthand indexes, it also works on array-likes:Array.prototype.myMap.call({ length: 2, 0: "a", 1: "b" }, (s) => s.toUpperCase())gives["A", "B"].
O(n) time and O(n) extra space for the result. Bonus point to raise yourself: assigning to Array.prototype creates an enumerable property, so for (const k in [7]) now yields "0" and "myMap". Real polyfills install methods with Object.defineProperty(Array.prototype, "myMap", { value: …, writable: true, configurable: true }), which is non-enumerable by default.
thisArg in myMapfunction