Most Asked Polyfills in JavaScript Interviews

Vivek Moradiya
7 min readMay 25, 2024

--

What are Polyfills?

Polyfills are pieces of code (usually JavaScript) that provide functionality on older browsers that do not natively support certain features. Essentially, polyfills allow developers to use modern web features and APIs while ensuring compatibility with older environments. They act as a bridge between newer and older versions of web technologies.

Why are Polyfills Needed?

  1. Browser Compatibility: Different browsers and different versions of the same browser may not support all the latest web standards. Polyfills help ensure that all users have a consistent experience regardless of the browser they use.
  2. Future-Proofing: As web standards evolve, using polyfills allows developers to write code that conforms to modern practices, knowing that it will still work in older environments.
  3. Development Convenience: Polyfills enable developers to use new features and syntax without waiting for full support across all browsers.

Topics Covered:

  1. Function.prototype.call
  2. Function.prototype.apply
  3. Function.prototype.bind
  4. Array.prototype.map
  5. Array.prototype.filter
  6. Array.prototype.reduce
  7. Array.prototype.forEach
  8. Debounce
  9. Throttle
  10. Memoize
  11. Promise.all

In JavaScript interviews, it’s common to discuss polyfills, which are code snippets that replicate the functionality of newer browser features for older browsers. Let’s explore some frequently asked polyfills and their implementations:

1. Call

The call method calls a function with a given this value and arguments provided individually.

Object.prototype.myCall = function(callObj, ...params) {
if (typeof this !== "function") {
throw new Error(this + " is not a Function");
}
callObj.tempFunction = this;
const result = callObj.tempFunction(...params);
delete callObj.tempFunction;
return result;
};

let object1 = {
name: "Vivek",
surname: "moradiya",
printName: function(age) {
return this.name + " " + this.surname + " " + age;
}
};

let object2 = {
name: "Amy",
surname: "Patel"
};

console.log(object1.printName.myCall(object2, 22)); // Amy Patel 22

Explanation: This polyfill temporarily assigns the function to the callObj object, calls it with the provided arguments, and then removes the temporary function to avoid polluting the object.

2. Apply

The apply method calls a function with a given this value and arguments provided as an array.

Object.prototype.myApply = function(applyObj, params) {
if (typeof this !== "function") {
throw new Error(this + " is not a Function");
}
applyObj.tempFunction = this;
const result = applyObj.tempFunction(...params);
delete applyObj.tempFunction;
return result;
};

let object1 = {
name: "Vivek",
surname: "moradiya",
printName: function(age, city) {
return this.name + " " + this.surname + " " + age + " " + city;
}
};

let object2 = {
name: "Amy",
surname: "Patel"
};

console.log(object1.printName.myApply(object2, [22, "morbi"])); // Amy Patel 22 morbi

Explanation: Similar to myCall, but myApply accepts an array of arguments which are spread when calling the function.

3. Bind

The bind method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Object.prototype.myBind = function(bindObj, ...params) {
if (typeof this !== "function") {
throw new Error(this + " is not a Function");
}
const self = this;

return function(...args) {
return self.apply(bindObj, [...params, ...args]);
};
};

let object1 = {
name: "Vivek",
surname: "moradiya",
printName: function(age, city) {
return this.name + " " + this.surname + " " + age + " " + city;
}
};

let object2 = {
name: "Amy",
surname: "Patel"
};

let boundFunction = object1.printName.myBind(object2, 22, "Morbi");
console.log(boundFunction()); // Amy Patel 22 Morbi

Explanation: The myBind function returns a new function. When this new function is called, it calls the original function with the bindObj as this and combines the bound parameters with any new ones.

4. Map

The map method creates a new array populated with the results of calling a provided function on every element in the calling array.

Array.prototype.myMap = function(callback, context) {
let arr = [];
for (let i = 0; i < this.length; i++) {
arr.push(callback.call(context, this[i], i, this));
}
return arr;
};

let arr = [1, 2, 4, 5, 6, 4];
let context = {
multiplier: 7,
offset: 10
};

let newArr = arr.myMap(function(value) {
return value * this.multiplier + this.offset;
}, context);

console.log(newArr); // [17, 24, 38, 45, 52, 38]

Explanation: We iterate over the array, applying the callback function to each element, and use call to set the context. The results are stored in a new array.

5. Filter

The filter method creates a new array with all elements that pass the test implemented by the provided function.

Array.prototype.myFilter = function(callback, context) {
let arr = [];
for (let i = 0; i < this.length; i++) {
if (callback.call(context, this[i], i, this)) {
arr.push(this[i]);
}
}
return arr;
};

let arr = [1, 2, 4, 5, 6, 4];
let context = {
condition: 5
};

let newArr = arr.myFilter(function(value) {
return value > this.condition;
}, context);

console.log(newArr); // [6]

Explanation: Similar to myMap, but myFilter only includes elements that pass the condition in the callback function

6. Reduce

The reduce method executes a reducer function on each element of the array, resulting in a single output value.

Array.prototype.myReduce = function(callback, acc) {
let output = acc;
let startIndex = 0;
if (output === undefined) {
output = this[0];
startIndex++;
}

for (let i = startIndex; i < this.length; i++) {
output = callback(output, this[i], i, this);
}

return output;
};

const friends = [
{ name: "Anna", books: ["Bible", "Harry Potter"] },
{ name: "Bob", books: ["War and peace", "Romeo and Juliet"] },
{ name: "Alice", books: ["The Lord of the Rings", "The Shining"] }
];
const allBooks = friends.myReduce((acc, cur) => [...acc, ...cur.books], []);

console.log(allBooks); // ["Bible", "Harry Potter", "War and peace", "Romeo and Juliet", "The Lord of the Rings", "The Shining"]

Explanation: We iterate over the array, applying the reducer function to accumulate the results. If no initial accumulator is provided, we use the first element of the array.

7. ForEach

The forEach method executes a provided function once for each array element.

Array.prototype.myForEach = function(callback) {
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this);
}
};

const arrData = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

arrData.myForEach((element) => {
console.log(element);
});

Explanation: We iterate over the array and call the provided callback function for each element.

8. Debounce

The debounce function ensures that a function is not called too frequently. It delays the function execution until after a certain period has elapsed since the last time it was invoked.

function debounce(fn, delay) {
let timeoutId;

return function(...args) {
if (timeoutId) {
clearTimeout(timeoutId);
}

timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

const debouncedFunction = debounce(() => {
console.log('Debounced function called');
}, 2000);

debouncedFunction();
debouncedFunction();
debouncedFunction();

Explanation: The debounce function sets a timeout each time it is called. If it is called again before the delay period ends, the previous timeout is cleared and a new one is set. This ensures that the function is only executed once the delay period has elapsed without another call.

9. Throttle

The throttle function ensures that a function is called at most once every specified period.

function throttle(fn, limit) {
let lastCall = 0;

return function(...args) {
const now = Date.now();

if (now - lastCall >= limit) {
lastCall = now;
fn.apply(this, args);
}
};
}

const throttledFunction = throttle(() => {
console.log('Throttled function called');
}, 2000);

throttledFunction();
throttledFunction();
throttledFunction();

Explanation: The throttle function checks the time elapsed since the last call. If it is greater than or equal to the limit, the function is executed and the last call time is updated.

10. Memoize

The memoize function caches the results of function calls to improve performance for expensive computations.

function myMemoize(fn) {
const cache = {};

return function(...args) {
let argCache = JSON.stringify(args);

if (!cache[argCache]) {
cache[argCache] = fn.call(this, ...args);
}

return cache[argCache];
};
}

const expensiveFunc = (num1, num2) => {
let output = 1;
for (let i = 0; i <= 10000000; i++) {
output += i;
}

return num1 + num2 + output;
}

const memoizeFunc = myMemoize(expensiveFunc);

console.time();
console.log(memoizeFunc(1, 2));
console.timeEnd();

console.time();
console.log(memoizeFunc(1, 2));
console.timeEnd();

Explanation: The myMemoize function uses a cache object to store the results of function calls. If the function is called again with the same arguments, the cached result is returned.

11. Promise.all

The Promise.all method takes an iterable of promises and returns a single promise that resolves when all of the promises resolve or rejects when any of the promises reject.

const p1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("resolved 1");
}, 1000);
});

const p2 = new Promise(function (resolve, reject) {
setTimeout(() => {
reject("rejected 2");
}, 2000);
});

const p3 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("resolved 3");
}, 3000);
});

const p4 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve("resolved 4");
}, 3000);
});

Promise.myAll = function (promises) {
return new Promise(function (resolve, reject) {
let result = [];
let total = 0;

promises.forEach((item, index) => {
Promise.resolve(item)
.then((res) => {
result[index] = res;
total++;
if (total === promises.length) resolve(result);
})
.catch((err) => {
reject(err);
});
});
});
};

Promise.myAll([p1, p2, p3])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

Promise.myAll([p1, p3, p4])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});

Explanation: The myAll function returns a new promise. It resolves with an array of results when all promises are resolved or rejects with the first encountered rejection.

Polyfills ensure that modern JavaScript code can run smoothly in all browsers, providing greater accessibility and functionality to users across different platforms. Understanding and implementing these polyfills demonstrates a developer’s commitment to maintaining high standards of web development.

--

--

Vivek Moradiya
Vivek Moradiya

Written by Vivek Moradiya

🚀 Full-stack developer specializing in the MERN stack – an expert in MongoDB, Express.js, React, Next Js and Node.js.

Responses (1)