Top JavaScript Polyfills: Interview Questions and Answers
Master JavaScript polyfills from basic concepts to advanced implementations, including call, apply, bind, map, filter, reduce, forEach, once, debounce, throttle, Promise methods, Object methods, and other commonly asked polyfill coding problems.
Q1: What is a polyfill in JavaScript?
Answer
A polyfill is a piece of JavaScript code that implements functionality that is missing in an environment. It allows developers to use certain APIs in environments that do not natively support them.
For example, if an environment does not support Array.prototype.myMap, we can implement a custom version that behaves similarly to the native map() method.
if (!Array.prototype.myMap) {
Array.prototype.myMap = function (callback, thisArg) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this) {
result.push(callback.call(thisArg, this[i], i, this));
}
}
return result;
};
}
console.log([1, 2, 3].myMap(n => n * 2));
// [2, 4, 6]
Important points
- A polyfill implements missing functionality using JavaScript.
- A polyfill generally aims to reproduce the behavior of a native API, including its edge cases.
- A polyfill is different from a transpiler. A transpiler transforms source code, whereas a polyfill provides missing runtime functionality.
- Production polyfills should follow the relevant ECMAScript specification as closely as practical.
- A polyfill supplies missing runtime functionality.
- Correct polyfills should reproduce native API behavior, not just the simplest use case.
Q2: Implement a polyfill for Array.prototype.map().
Answer
The map() method creates a new array by applying a callback to each existing element of the original array. It does not mutate the original array.
A good polyfill should support the callback arguments value, index, and array, an optional thisArg, and sparse arrays.
Array.prototype.myMap = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.myMap called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
const result = new Array(length);
for (let i = 0; i < length; i++) {
if (i in array) {
result[i] = callback.call(thisArg, array[i], i, array);
}
}
return result;
};
console.log([1, 2, 3].myMap(n => n * 2));
// [2, 4, 6]
How it works
- Validate the receiver and callback.
- Convert the receiver to an object and capture its length.
- Create a result array with the same length.
- Visit only indexes that exist in the source array.
- Store the callback result at the corresponding index.
- Return the new array.
The i in array check preserves holes in sparse arrays. The unsigned right shift used above is a common interview simplification for obtaining a 32-bit unsigned length; it is not fully specification-compliant for every possible array-like object. A production-quality implementation should use LengthOfArrayLike semantics.
const numbers = [1, 2, 3, 4];
const doubled = numbers.myMap((value, index, array) => {
console.log(index, array === numbers);
return value * 2;
});
console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4]- Map returns a new array and preserves the source array.
- A robust implementation handles sparse arrays, callback validation, and thisArg.
Q3: Implement a polyfill for Array.prototype.filter().
Answer
The filter() method creates a new array containing the elements for which the callback returns a truthy value.
Array.prototype.myFilter = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.myFilter called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
const result = [];
for (let i = 0; i < length; i++) {
if (i in array) {
const value = array[i];
if (callback.call(thisArg, value, i, array)) {
result.push(value);
}
}
}
return result;
};
console.log([1, 2, 3, 4, 5].myFilter(n => n % 2 === 0));
// [2, 4]
Explanation
Unlike map(), which produces one output slot for every existing input element, filter() only includes elements that pass the condition.
The implementation skips holes in sparse arrays and passes the value, index, and original array to the callback.
- Filter returns a new array containing only matching elements.
- The callback's truthiness determines whether an element is retained.
Q4: Implement a polyfill for Array.prototype.forEach().
Answer
The forEach() method executes a callback for every existing element in an array. It returns undefined and does not build a result array.
Array.prototype.myForEach = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.myForEach called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
for (let i = 0; i < length; i++) {
if (i in array) {
callback.call(thisArg, array[i], i, array);
}
}
};
const result = [10, 20, 30].myForEach((value, index) => {
console.log(index, value);
});
console.log(result); // undefined
Explanation
- The callback executes for each existing index.
- The original array is not automatically modified, although the callback can modify it.
forEach()cannot be stopped early withbreakor a callback return value.- The iteration length is captured before the loop, matching native behavior more closely than repeatedly reading the length.
- ForEach executes callbacks for side effects and returns undefined.
- A callback's return value does not control iteration.
Q5: Implement a polyfill for Array.prototype.reduce().
Answer
The reduce() method processes array elements and accumulates them into a single result.
The accumulator starts with the supplied initial value when one is provided. Otherwise, it starts with the first existing array element.
Array.prototype.myReduce = function (callback, initialValue) {
if (this == null) {
throw new TypeError("Array.prototype.myReduce called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
let index = 0;
let accumulator;
if (arguments.length >= 2) {
accumulator = initialValue;
} else {
while (index < length && !(index in array)) {
index++;
}
if (index >= length) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = array[index++];
}
while (index < length) {
if (index in array) {
accumulator = callback(
accumulator,
array[index],
index,
array
);
}
index++;
}
return accumulator;
};
console.log([1, 2, 3, 4].myReduce((sum, n) => sum + n, 0));
// 10
Important interview edge cases
Empty array with an initial value: returns the initial value.
Empty array without an initial value: throws a TypeError.
Array without an initial value: the first existing element becomes the initial accumulator, and iteration starts after that element.
The implementation preserves sparse-array behavior. As with the earlier examples, length >>> 0 is a simplified length conversion rather than an exact implementation of the ECMAScript abstract operations.
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
const counts = fruits.myReduce((result, fruit) => {
result[fruit] = (result[fruit] || 0) + 1;
return result;
}, {});
console.log(counts);
// { apple: 3, banana: 2, orange: 1 }- Reduce carries an accumulator across iterations.
- Without an initial value, an empty array or an array containing only holes throws a TypeError.
Q6: Implement a polyfill for Array.prototype.some().
Answer
The some() method returns true as soon as at least one existing array element satisfies the callback condition. If no element matches, it returns false.
Array.prototype.mySome = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.mySome called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
for (let i = 0; i < length; i++) {
if (i in array && callback.call(thisArg, array[i], i, array)) {
return true;
}
}
return false;
};
console.log([1, 3, 5, 8].mySome(n => n % 2 === 0));
// true
Explanation
The method short-circuits on the first matching element. It returns false for an empty array because no element satisfies the condition.
- Some checks whether at least one element matches.
- It short-circuits and returns a boolean.
Q7: Implement a polyfill for Array.prototype.every().
Answer
The every() method returns true if every existing element satisfies the callback condition. It returns false immediately when an element fails.
Array.prototype.myEvery = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.myEvery called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
for (let i = 0; i < length; i++) {
if (i in array && !callback.call(thisArg, array[i], i, array)) {
return false;
}
}
return true;
};
console.log([2, 4, 6].myEvery(n => n % 2 === 0));
// true
Explanation
every() is the logical counterpart of some(). It short-circuits on the first failing element. An empty array returns true, which is known as vacuous truth.
- Every checks whether all existing elements satisfy a condition.
- Every returns true for an empty array.
Q8: Implement a polyfill for Array.prototype.find().
Answer
The find() method returns the first array element for which the callback returns a truthy value. If no element matches, it returns undefined.
Unlike map(), filter(), and forEach(), find() invokes the callback for every index below the captured length, including holes in sparse arrays. A hole is read as undefined.
Array.prototype.myFind = function (callback, thisArg) {
if (this == null) {
throw new TypeError("Array.prototype.myFind called on null or undefined");
}
if (typeof callback !== "function") {
throw new TypeError("Callback must be a function");
}
const array = Object(this);
const length = array.length >>> 0;
for (let i = 0; i < length; i++) {
const value = array[i];
if (callback.call(thisArg, value, i, array)) {
return value;
}
}
return undefined;
};
console.log([5, 12, 8, 130].myFind(n => n > 10));
// 12
Explanation
The loop stops immediately when a matching element is found. Returning from the function avoids unnecessary callback executions.
- Find returns the first matching value, not an array.
- Unlike several other array iteration methods, find visits sparse-array indexes as undefined.
Q9: Implement a polyfill for Array.prototype.includes().
Answer
The includes() method checks whether an array contains a value. It uses SameValueZero equality, meaning NaN matches NaN, and positive and negative zero are treated as equal.
Array.prototype.myIncludes = function (searchElement, fromIndex = 0) {
if (this == null) {
throw new TypeError("Array.prototype.myIncludes called on null or undefined");
}
const array = Object(this);
const length = array.length >>> 0;
if (length === 0) {
return false;
}
let index = Number(fromIndex);
if (Number.isNaN(index)) {
index = 0;
}
index = index < 0 ? Math.ceil(index) : Math.floor(index);
if (index >= length) {
return false;
}
if (index < 0) {
index = Math.max(length + index, 0);
}
for (let i = index; i < length; i++) {
const value = array[i];
if (
value === searchElement ||
(value !== value && searchElement !== searchElement)
) {
return true;
}
}
return false;
};
console.log([1, 2, NaN].myIncludes(NaN));
// true
console.log([1, 2, 3].myIncludes(2, -2));
// true
Explanation
The expression value !== value identifies NaN, because NaN is the only JavaScript value that is not equal to itself. This implementation covers common cases, but exact native compatibility requires careful handling of conversions and array-like lengths.
- Includes uses SameValueZero comparison and can find NaN.
- Negative fromIndex values count backward from the array's length.
Q10: Implement a polyfill for Array.prototype.flat().
Answer
The flat() method creates a new array with nested arrays flattened to a specified depth. Its default depth is 1.
Array.prototype.myFlat = function (depth = 1) {
if (this == null) {
throw new TypeError("Array.prototype.myFlat called on null or undefined");
}
const array = Object(this);
const result = [];
const maxDepth = Math.floor(Number(depth));
const actualDepth = Number.isNaN(maxDepth)
? 0
: Math.max(maxDepth, 0);
function flatten(items, remainingDepth) {
for (let i = 0; i < items.length; i++) {
if (!(i in items)) {
continue;
}
const value = items[i];
if (Array.isArray(value) && remainingDepth > 0) {
flatten(value, remainingDepth - 1);
} else {
result.push(value);
}
}
}
flatten(array, actualDepth);
return result;
};
console.log([1, [2, [3, [4]]]].myFlat(2));
// [1, 2, 3, [4]]
Explanation
The recursive function traverses nested arrays while the remaining depth is greater than zero. It appends non-array values and arrays encountered at the requested depth to the result.
This is an interview-oriented implementation. Native flat() has more detailed coercion rules for depth and supports generic array-like receivers.
- Flat recursively processes nested arrays up to the requested depth.
- Sparse slots in flattened arrays are skipped.