#Arrays#Polyfill

Array.prototype.map Polyfill

Implement Array.prototype.map from scratch. Master callback signatures, thisArg binding, and sparse-array hole preservation.

By Pratik RaiEasy

Array.prototype.map builds a new array by running a callback over every element. Reimplementing it looks like a five-line for loop, and the five-line version is wrong in at least four ways. Working out which four is the point of the exercise.

Why implement it yourself

map is the method people reach for without thinking, which makes it a good lens on how array methods actually behave. Almost everyone can write the loop. Far fewer can say what happens when the array has holes, when the callback deletes elements mid-iteration, or when map is called on something that is not an array at all.

Those are not trivia questions. They describe real behaviour you will eventually hit, usually while debugging something that makes no sense.

The naive version, and why it falls short

Here is what most people write first:

JSfile.javascript
1Array.prototype.myMap = function (callback) { 2 const result = []; 3 for (let i = 0; i < this.length; i++) { 4 result.push(callback(this[i], i, this)); 5 } 6 return result; 7};

This produces the right answer for a dense array of values. It diverges from the native method the moment anything unusual happens.

What the specification actually requires

Sparse arrays keep their holes. An array literal like [1, , 3] has a hole at index 1 — not undefined, an actual absence. Native map skips holes without calling the callback and preserves them in the output. The naive version calls the callback with undefined and fills the hole in. The fix is an in check:

JSfile.javascript
1if (i in this) { 2 result[i] = callback.call(thisArg, this[i], i, object); 3}

Assigning by index rather than using push is what preserves the hole positions.

The length is captured once. The spec reads length before iterating. If the callback pushes new elements, those are never visited — otherwise map on a growing array would never terminate. Elements deleted or changed mid-iteration, however, are observed, because each index is read fresh.

The second argument sets this. map(callback, thisArg) binds this inside the callback. Forgetting this is the most common omission.

It works on array-likes. map is intentionally generic. Array.prototype.map.call({0: 'a', 1: 'b', length: 2}, fn) works. That is why the implementation begins by coercing with Object(this) and reading length as an unsigned integer rather than assuming a real array.

A more faithful implementation

JSfile.javascript
1Array.prototype.myMap = function (callback, thisArg) { 2 if (this === null || this === undefined) { 3 throw new TypeError('Array.prototype.myMap called on null or undefined'); 4 } 5 if (typeof callback !== 'function') { 6 throw new TypeError(`${callback} is not a function`); 7 } 8 9 const object = Object(this); 10 const length = object.length >>> 0; 11 const result = new Array(length); 12 13 for (let i = 0; i < length; i++) { 14 if (i in object) { 15 result[i] = callback.call(thisArg, object[i], i, object); 16 } 17 } 18 19 return result; 20};

>>> 0 is the idiomatic way to coerce length to a 32-bit unsigned integer, matching the spec's ToUint32. It turns -1, NaN and "3" into sane values, and it is the reason map on an object with length: -1 returns an empty array instead of looping forever.

new Array(length) pre-sizes the result so that untouched indices stay holes rather than becoming undefined.

Edge cases worth knowing

map always returns the same length as its input. It cannot filter. [1,2,3].map(x => x > 1 ? x : undefined) gives you [undefined, 2, 3], not [2, 3]. Reaching for map when you meant filter or flatMap is a common source of stray undefined values.

The classic parseInt trap. ['1','2','3'].map(parseInt) returns [1, NaN, NaN]. map passes three arguments and parseInt takes two, so the index becomes the radix. This is the single most-asked map interview question, and it is really a question about how many arguments a callback receives.

this inside an arrow callback ignores thisArg. Arrow functions close over this lexically, so the second parameter has no effect on them. If you need thisArg, you need a regular function.

Key takeaways

  • Read length once, up front, and coerce it with >>> 0.
  • Use i in object to skip holes, and index assignment to preserve them.
  • Support the thisArg parameter, and remember arrows ignore it.
  • map is generic — it works on anything with a length and indexed keys.
  • The callback receives (value, index, array), which is why map(parseInt) misbehaves.

Goal: Implement myMap in under 15 minutes. Make sure all three callback arguments work and thisArg is respected.

Frequently asked questions

Why do interviewers ask you to write a map polyfill?
Because implementing `map` yourself exposes whether you know what the built-in actually guarantees: a new array, the original untouched, three arguments to the callback, and correct behaviour on sparse arrays. It is a small function that separates people who have read the specification from people who have only used the method.
What does Array.prototype.map pass to the callback?
Three arguments, in order: the element, its index, and the array itself. Forgetting the second and third is the single most common gap, and it is usually the first thing an interviewer checks by writing a callback that uses the index.
How should a map polyfill handle holes in a sparse array?
`map` skips holes but keeps them in the result, so the output array has the same length with the gaps still empty. Guarding each iteration with an `in` check reproduces that; iterating blindly turns holes into `undefined` values and quietly changes the result.
What is thisArg in map and why does it matter?
It is the optional second argument that becomes `this` inside the callback. Supporting it means calling the callback with `call` and passing that value through rather than invoking it directly — a one-line difference that interviewers look for because it shows you read past the first parameter.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

Array.prototype.filter Polyfill

Implement Array.prototype.filter from scratch. The key detail: push the original element, not the boolean result of the predicate.

JavaScript · Arrays · PolyfillsPratik Rai ·

JavaScript

Array.prototype.flat Polyfill

Implement Array.prototype.flat from scratch with a configurable depth. Default depth is 1, not Infinity.

JavaScript · Arrays · RecursionPratik Rai ·

JavaScript

Array.prototype.reduce Polyfill

Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.

JavaScript · Arrays · PolyfillsPratik Rai ·