On this page · sections
JavaScript Core JS Polyfill
Six famous polyfills:
call,apply,bind,map,filter, andreduce.
Level: Medium · Coding Round · Core JS Polyfill
A polyfill is your own version of a built-in. These six show up in almost every JavaScript coding round.
Why These Six Matter
call— run a function now and choosethis.apply— same ascall, but the arguments come in an array.bind— do not run yet. Return a function that already hasthis.map— new array, same length, one new value per item.filter— new shorter array, only the items that pass.reduce— fold the array into one value. A start of0still counts.
map and filter use call on every index. Learn this first, then the three array walks.
Questions
| # | Write | In one line |
|---|---|---|
| 1 | call | Run the function now, and choose this |
| 2 | apply | Same as call, but arguments arrive in an array |
| 3 | bind | Do not run yet. Return a function with this already chosen |
| 4 | map | New array, same length, one new value per item |
| 5 | filter | New shorter array, only the items that pass |
| 6 | reduce | Fold the array into one value |
1. call
Run the function immediately. You pass the object that should be this, then the arguments one by one.
function greet(msg) {
return `${msg}, ${this.name}`;
}
greet.myCall({ name: "Ada" }, "Hello");
// "Hello, Ada"
// this === { name: "Ada" }How
- If they passed
nullor nothing, useglobalThis. - If they passed a number or string, turn it into an object.
- Hang the function on that object and call it. That is what sets
this. - Remove it afterwards, even if the function throws.
Function.prototype.myCall = function (thisArg, ...args) {
if (typeof this !== "function") {
throw new TypeError("myCall must be called on a function");
}
const ctx =
thisArg == null ? globalThis : Object(thisArg);
const key = Symbol();
ctx[key] = this;
try {
return ctx[key](...args);
} finally {
delete ctx[key];
}
};Symbol() is a private property name, so you do not overwrite name or id. Arrow functions ignore the this you pass.
2. apply
Same as call. The only change: arguments come in one array, not as separate parameters.
Math.max.myApply(null, [1, 5, 3]);
// 5Function.prototype.myApply = function (thisArg, argsList) {
if (typeof this !== "function") {
throw new TypeError("myApply must be called on a function");
}
const args = argsList == null ? [] : Array.from(argsList);
return this.myCall(thisArg, ...args);
};No list means “call with zero arguments.” Array.from also accepts array-like objects such as arguments.
call(obj, a, b) → separate arguments
apply(obj, [a, b]) → one list3. bind
Do not run the function. Return a new function that already knows its this, and any arguments you want to lock in now.
function greet(greeting, name) {
return `${greeting}, ${name} — ${this.role}`;
}
const hi = greet.myBind({ role: "admin" }, "Hi");
hi("Ada");
// "Hi, Ada — admin"
// "Hi" was saved. "Ada" is passed later.Two ways it can be called
hi("Ada") → this is the object you bound
new Admin("x") → this is the new object. Ignore the bound object.Function.prototype.myBind = function (thisArg, ...preset) {
if (typeof this !== "function") {
throw new TypeError("myBind must be called on a function");
}
const fn = this;
function bound(...args) {
const ctx = new.target ? this : thisArg;
return fn.apply(ctx, [...preset, ...args]);
}
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
return bound;
};new.target is set only when someone writes new bound(). Copying prototype keeps instanceof working. Calling bound.call(somethingElse) does not change the bound this.
4. map
Build a new array of the same length. Run fn on each real item. Do not change the original array.
myMap([1, 2, 3], (x) => x * 2);
// [2, 4, 6]A hole is a missing index, not the value undefined.
input: [1, <empty>, 3]
map: [2, <empty>, 6] same placesfunction myMap(array, fn, thisArg) {
const out = new Array(array.length);
for (let i = 0; i < array.length; i++) {
if (!(i in array)) continue;
out[i] = fn.call(thisArg, array[i], i, array);
}
return out;
}new Array(length) starts with empty slots, so skipped indexes stay empty. fn receives (value, index, array). thisArg is the this inside a normal function. An arrow function ignores it.
5. filter
Walk the same way as map, but keep only the items that pass. The result is packed: no empty slots.
myFilter([1, 2, 3, 4], (x) => x % 2 === 0);
// [2, 4]input: [1, <empty>, 2, 4]
filter: [2, 4] empty slots are droppedfunction myFilter(array, fn, thisArg) {
const out = [];
for (let i = 0; i < array.length; i++) {
if (!(i in array)) continue;
if (fn.call(thisArg, array[i], i, array)) {
out.push(array[i]);
}
}
return out;
}Check i in array, not array[i] == null. A real stored undefined is a value. A missing index is a hole.
map → keep the same indexes
filter → keep only the matches, in order6. reduce
Turn the whole array into one result. You either pass a starting value, or the first item becomes the start.
myReduce([1, 2, 3], (sum, n) => sum + n, 0);
// 0 + 1 + 2 + 3 = 6
myReduce([1, 2, 3], (sum, n) => sum + n);
// start at 1, then + 2 + 3 = 6seed given → start at index 0, seed is the first total
no seed → start at index 1, array[0] is the first total
empty, no seed → throw0 is a real starting value. Do not write if (initial) — that treats 0 as “missing.”
function myReduce(array, fn, initial) {
let acc;
let i = 0;
if (arguments.length >= 3) {
acc = initial;
} else {
if (!array.length) {
throw new TypeError("Reduce of empty array");
}
acc = array[0];
i = 1;
}
for (; i < array.length; i++) {
if (i in array) {
acc = fn(acc, array[i], i, array);
}
}
return acc;
}arguments.length >= 3 means “they passed a third argument,” even when that argument is 0, false, null, or undefined.
Say this
“
callruns now: I put the function on the object and call it, then delete it.applyis the same with an argument list.bindwaits: it returns a function that already hasthis, unless you usenew.mapkeeps every index.filterkeeps only matches.reducechecks whether a start value was passed —0still counts.”