On this page
Tracks

Polyfill: call, apply and bindhigh-yield

Last reviewed 22 Sept 2026

Problem

Implement myCall, myApply and myBind on Function.prototype so they behave like the built-ins:

function greet(greeting, punct) { return `${greeting}, ${this.name}${punct}`; }
const user = { name: 'Asha' };
greet.myCall(user, 'Hi', '!'); // "Hi, Asha!"
greet.myApply(user, ['Hello', '.']); // "Hello, Asha."
const hi = greet.myBind(user, 'Hey');
hi('?'); // "Hey, Asha?"

Clarifying questions

  • Should null / undefined as thisArg fall back to the global object (sloppy mode) or stay undefined (strict)? Usually: fall back to globalThis.
  • Should primitives ('abc', 42) as thisArg be boxed? Yes — Object(thisArg).
  • Must a bound function work with new? Ask — it separates good answers from great ones.
  • Can I use the built-in call/apply inside? No — that defeats the point.

Approach

A function called as a method (obj.fn()) gets this === obj. So to call fn with a chosen this, temporarily attach it to that object under a key that cannot collide (a Symbol), call it, then delete the key. apply is the same with an array of arguments. bind returns a new function that remembers this and the first arguments, and calls the original with apply later.

Step-by-step build

Step 1 — myCall

Function.prototype.myCall = function (thisArg, ...args) {
const ctx = thisArg == null ? globalThis : Object(thisArg);
const key = Symbol('fn'); // unique: never overwrites a real property
ctx[key] = this; // `this` is the function being called
const result = ctx[key](...args);
delete ctx[key];
return result;
};

Step 2 — myApply

The only difference is that arguments arrive as an array (or null).

Function.prototype.myApply = function (thisArg, args = []) {
return this.myCall(thisArg, ...(args ?? []));
};

Step 3 — myBind with partial application

Function.prototype.myBind = function (thisArg, ...preset) {
const fn = this;
return function (...later) {
return fn.myApply(thisArg, [...preset, ...later]);
};
};

Step 4 — support new

With the native bind, new bound() ignores the bound this and builds a fresh instance of the original function. Detect this with new.target, and link prototypes so instanceof still works.

Function.prototype.myBind = function (thisArg, ...preset) {
if (typeof this !== 'function') throw new TypeError('Bind must be called on a function');
const fn = this;
function bound(...later) {
if (new.target) return new fn(...preset, ...later); // called with new
return fn.myApply(thisArg, [...preset, ...later]);
}
if (fn.prototype) bound.prototype = Object.create(fn.prototype);
return bound;
};

Final code

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('fn');
ctx[key] = this;
try {
return ctx[key](...args);
} finally {
delete ctx[key]; // clean up even if the function throws
}
};
Function.prototype.myApply = function (thisArg, args) {
return this.myCall(thisArg, ...(args ?? []));
};
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(...later) {
if (new.target) return new fn(...preset, ...later);
return fn.myApply(thisArg, [...preset, ...later]);
}
if (fn.prototype) bound.prototype = Object.create(fn.prototype);
return bound;
};

Edge cases

  • thisArg is null/undefined → globalThis; a primitive → boxed with Object().
  • The target object is frozen — assigning the temporary key fails. Mention it; the real call has no such limit because it is implemented natively.
  • The function throws → the finally still removes the temporary key.
  • Binding twice: f.myBind(a).myBind(b) keeps a — the first bound this wins, as with the native bind.
  • Arrow functions ignore this entirely; all three only forward the arguments.

Follow-ups

  • Why a Symbol instead of ctx.fn? A string key could overwrite an existing property.
  • What does bound.length return? Native bind sets it to max(0, fn.length − preset.length); set it with Object.defineProperty if asked.
  • Implement it without spread (ES5)? Build the argument list as a string and eval — mention it only as history.

Common mistakes

  • Using an arrow function for the polyfill itself: then this is not the function being called.
  • Forgetting to delete the temporary property, leaving the object polluted.
  • bind that only supports this and not preset arguments.
  • Not returning the function’s result.