Implement Function.prototype.call

A call polyfill invokes a function with an explicit this and a list of arguments. Inside the polyfill, this is the function being called. The classic approach assigns it to a temporary property on the target and deletes it afterwards; Reflect.apply does the same job without touching the object at all.

Easy to hard this binding prototype methods rest arguments

The question

Asked to test whether you really understand this. The trap is baked into the question: the obvious implementation works, and then the interviewer asks what it did to the object you passed in.

  1. Write myCall so fn.myCall(obj, a, b) works.
  2. Prove the target object is unchanged afterwards.
  3. Handle the case where myCall itself is passed around before being used.

The solution

Reflect.apply is the honest answer. If the interviewer wants the temporary-property version, the note below has it.

Function.prototype.myCall = function (thisArg, ...argArray) {
  if (typeof this !== "function") {
    throw new TypeError("myCall must be called on a function");
  }
  // Reflect.apply invokes with the given receiver without ever assigning the
  // function to a temporary property, so the target object is left untouched.
  return Reflect.apply(this, thisArg, argArray);
};

How it works

Inside the method, this is the function. That’s the whole conceptual leap: introduce.myCall(...) means this === introduce. From there, Reflect.apply(fn, thisArg, args) invokes with an explicit receiver and never assigns anything anywhere, so the target is untouched by construction rather than by cleanup. Rest parameters collect the arguments, which is the difference between call and apply.

The tests it has to pass

This is the part most explanations leave out. "It works" is not a claim you can check by reading. These are the 5 cases Practice Pad runs against your solution, and the implementation above passes all of them.

Easy2 cases · receiver and arguments

runs the function with the supplied object and arguments

function introduce(greeting, punctuation) {
  return greeting + ", " + this.name + punctuation;
}
expectEqual(introduce.myCall({ name: "Ada" }, "Hello", "!"), "Hello, Ada!");

passes every argument through in order

function join3(a, b, c) {
  return [this.prefix, a, b, c].join("-");
}
expectEqual(join3.myCall({ prefix: "p" }, 1, 2, 3), "p-1-2-3");
Hard3 cases · purity and detachment

returns the computed value across repeated calls

function describeTeam() {
  return this.title + " has " + String(this.members.length);
}
const target = { title: "Solo", members: ["x"] };
expectEqual([describeTeam.myCall(target), describeTeam.myCall(target)], ["Solo has 1", "Solo has 1"]);

does not leave extra properties behind on the supplied object

const target = { name: "Ada" };
function nameOf() {
  return this.name;
}
nameOf.myCall(target);
expectEqual(Object.keys(target), ["name"]);

works when the call itself is passed around before being used

function describe(suffix) {
  return this.label + suffix;
}
const borrowed = describe.myCall;
expectEqual(borrowed.call(describe, { label: "core" }, "!"), "core!");

Edge cases to watch out for

Assigning to a fixed property and forgetting to delete it

thisArg.fn = this; const result = thisArg.fn(...args); works, and leaves a stray fn key behind. One of the hard tests checks Object.keys(target) for exactly this reason.

Using a string key that might already exist

Even with a delete, a key named fn could overwrite a real property and then remove it, corrupting the object. A Symbol() is unique and non-enumerable in Object.keys, which is why the classic answer uses one.

Not handling a primitive or null thisArg

In sloppy mode the real call boxes primitives and substitutes the global object for null. In strict mode, and inside a module, it passes them through unchanged. Knowing which mode you’re in is the point.

The classic version, if asked for it without Reflect: create const key = Symbol(), assign thisArg[key] = this, call thisArg[key](...argArray), store the result, delete thisArg[key], and return. A Symbol key never collides and never shows up in Object.keys.

Follow up questions

What’s the difference between call, apply and bind?

call invokes immediately with arguments listed individually. apply invokes immediately with arguments in an array. bind invokes nothing: it returns a new function with this permanently fixed, which you call later.

Why use a Symbol for the temporary property?

A string key can collide with a real property on the target, and would show up in Object.keys if the delete were ever skipped. A Symbol is guaranteed unique and isn’t enumerated by Object.keys or JSON.stringify.

How would you implement bind?

Return a new function rather than invoking: capture this and the leading arguments in a closure, and on call, apply the original with the captured receiver and the captured arguments concatenated with the new ones. The tricky part is that a bound function used with new must ignore the bound receiver, which is the follow-up interviewers reach for.

What happens when thisArg is null or a primitive?

In sloppy mode the value is coerced: null and undefined become the global object, and primitives are wrapped in their object form. In strict mode and inside modules they’re passed through exactly as given, which is almost always what you want.

Practise this with the tests running

Practice Pad runs these 5 cases against your own solution, in a real editor, on your Mac. Twenty-four questions, and an optional AI panel that explains why a test failed.

Download for macOS Free. Requires macOS 12 or later on Apple Silicon.

See all 24 questions