Polyfills
Implement built-in JavaScript methods from scratch — the ultimate interview coding challenge
Last updated on
A polyfill is a piece of code that implements a feature that the browser/environment doesn't natively support. Writing polyfills demonstrates deep understanding and is a top interview coding task.
Array.prototype.map
Array.prototype.myMap = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this) { // handle sparse arrays
result.push(callback(this[i], i, this));
}
}
return result;
};
// Test
[1, 2, 3].myMap(n => n * 2); // [2, 4, 6]Array.prototype.filter
Array.prototype.myFilter = function (callback) {
const result = [];
for (let i = 0; i < this.length; i++) {
if (i in this && callback(this[i], i, this)) {
result.push(this[i]);
}
}
return result;
};
// Test
[1, 2, 3, 4, 5].myFilter(n => n > 3); // [4, 5]Array.prototype.reduce
Array.prototype.myReduce = function (callback, initialValue) {
let accumulator;
let startIndex;
if (initialValue !== undefined) {
accumulator = initialValue;
startIndex = 0;
} else {
if (this.length === 0) throw new TypeError("Reduce of empty array with no initial value");
accumulator = this[0];
startIndex = 1;
}
for (let i = startIndex; i < this.length; i++) {
if (i in this) {
accumulator = callback(accumulator, this[i], i, this);
}
}
return accumulator;
};
// Test
[1, 2, 3, 4].myReduce((sum, n) => sum + n, 0); // 10Array.prototype.flat
Array.prototype.myFlat = function (depth = 1) {
const result = [];
function flatten(arr, currentDepth) {
for (const item of arr) {
if (Array.isArray(item) && currentDepth < depth) {
flatten(item, currentDepth + 1);
} else {
result.push(item);
}
}
}
flatten(this, 0);
return result;
};
// Test
[1, [2, [3, [4]]]].myFlat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].myFlat(Infinity); // [1, 2, 3, 4]Function.prototype.bind
Function.prototype.myBind = function (context, ...bindArgs) {
const fn = this;
return function (...callArgs) {
return fn.apply(context, [...bindArgs, ...callArgs]);
};
};
// Test
function greet(greeting, punct) {
return `${greeting}, ${this.name}${punct}`;
}
const boundGreet = greet.myBind({ name: "Shiva" }, "Hello");
boundGreet("!"); // "Hello, Shiva!"Function.prototype.call
Function.prototype.myCall = function (context = globalThis, ...args) {
const key = Symbol("fn");
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};Function.prototype.apply
Function.prototype.myApply = function (context = globalThis, args = []) {
const key = Symbol("fn");
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};Promise (Simplified)
class MyPromise {
#state = "pending";
#value = undefined;
#callbacks = [];
constructor(executor) {
const resolve = (value) => {
if (this.#state !== "pending") return;
this.#state = "fulfilled";
this.#value = value;
this.#callbacks.forEach(cb => cb.onFulfilled(value));
};
const reject = (reason) => {
if (this.#state !== "pending") return;
this.#state = "rejected";
this.#value = reason;
this.#callbacks.forEach(cb => cb.onRejected(reason));
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = () => {
try {
if (this.#state === "fulfilled") {
const result = onFulfilled ? onFulfilled(this.#value) : this.#value;
resolve(result);
} else if (this.#state === "rejected") {
if (onRejected) {
const result = onRejected(this.#value);
resolve(result);
} else {
reject(this.#value);
}
}
} catch (error) {
reject(error);
}
};
if (this.#state === "pending") {
this.#callbacks.push({
onFulfilled: () => handle(),
onRejected: () => handle()
});
} else {
queueMicrotask(handle);
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}Debounce
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}Throttle
function throttle(fn, limit) {
let inThrottle = false;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => { inThrottle = false; }, limit);
}
};
}Deep Clone
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (Array.isArray(obj)) return obj.map(item => deepClone(item));
const clone = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) return resolve([]);
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(value) => {
results[index] = value;
completed++;
if (completed === promises.length) resolve(results);
},
(reason) => reject(reason)
);
});
});
}Interview Tip
When implementing polyfills in interviews:
- Start with the function signature
- Handle edge cases (empty arrays, no initial value)
- Use
thiscorrectly (the array the method is called on) - Test with a simple example
- Discuss time/space complexity