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.
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.
- Write
myCallsofn.myCall(obj, a, b)works. - Prove the target object is unchanged afterwards.
- Handle the case where
myCallitself 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.
✓ 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");
✓ 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.
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.