#this binding#Polyfill

Function.prototype.call Polyfill

Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.

By Pratik RaiMedium

Function.prototype.call invokes a function immediately with an explicit this value and positional arguments. Rebuilding it from scratch is one of the fastest ways to understand how JavaScript actually resolves this, why primitives get boxed, and how easy it is to leave temporary state behind on someone else's object.

Why implement it yourself

You will never ship a call polyfill — every engine has had it since ES3. The value is in what it forces you to confront. A correct implementation touches execution context, property lookup, primitive wrappers, and cleanup. Get one of those wrong and the bug is subtle: your function works on plain objects and quietly corrupts frozen ones, or leaks a property that a later Object.keys picks up.

It also comes up constantly in interviews, and the interesting part is never the happy path. It is the follow-up questions.

What the native method actually does

call looks simple from the outside:

JSfile.javascript
1function greet(greeting, punctuation) { 2 return `${greeting}, ${this.name}${punctuation}`; 3} 4 5greet.call({ name: 'Ada' }, 'Hello', '!'); // "Hello, Ada!"

Underneath, the specification does several things that are easy to miss:

  • It throws a TypeError if the target is not callable.
  • In non-strict mode, a thisArg of null or undefined is replaced with the global object.
  • In non-strict mode, a primitive thisArg is coerced to its object wrapper — 3 becomes a Number object.
  • The function's return value passes straight through.

Those coercion rules are sloppy-mode behaviour. Inside a strict-mode function, this is left exactly as passed, so null stays null. A polyfill written in a module (modules are always strict) cannot fully reproduce sloppy-mode semantics — a genuinely interesting thing to be able to say out loud in an interview.

The implementation

The trick is that there is no way to invoke a function with an arbitrary this without using the mechanism that sets this in the first place: a method call. So you temporarily attach the function to the target object, call it as a method, then remove it.

JSfile.javascript
1Function.prototype.myCall = function (thisArg, ...args) { 2 if (typeof this !== 'function') { 3 throw new TypeError('myCall must be called on a function'); 4 } 5 6 const context = 7 thisArg === null || thisArg === undefined ? globalThis : Object(thisArg); 8 9 const key = Symbol('fn'); 10 context[key] = this; 11 12 try { 13 return context[key](...args); 14 } finally { 15 delete context[key]; 16 } 17};

Object(thisArg) handles the boxing rule in one step: it returns objects unchanged and wraps primitives. globalThis covers the null/undefined default portably across browsers, Node and workers.

The edge cases most implementations miss

Property collisions. Many tutorials use a string key like context.fn = this. If the caller's object already has an fn property, you have just destroyed it — and delete afterwards removes the original too. A Symbol cannot collide with anything, which is why it is the right key here.

Cleanup on throw. If the invoked function throws, a naive implementation never reaches its delete line and leaves the temporary property attached forever. Wrapping the call in try/finally guarantees cleanup on both paths. This is the detail that separates a working answer from a correct one.

Non-extensible objects. If thisArg is frozen or sealed, assigning the temporary property silently fails in sloppy mode and throws in strict mode. The spec-accurate approach avoids mutation entirely, but the mutation-based version is what interviewers are usually looking for — worth naming the limitation rather than pretending it does not exist.

Primitives are copies. Because Object(3) creates a fresh wrapper, mutations to this inside the function do not propagate back. That is genuinely how the native method behaves, not a flaw in the polyfill.

Common follow-ups

"Now write apply." Nearly identical — it takes an array instead of a rest parameter, so return context[key](...(argArray || [])). The || [] matters: apply accepts null for its second argument.

"Now write bind." Meaningfully harder. bind returns a new function rather than invoking one, has to support partial application across two call sites, and must behave correctly when the bound function is used with new — in which case the bound this is ignored in favour of the newly constructed object.

"What if the function is an arrow function?" Arrow functions capture this lexically and ignore it entirely at call time. call, apply and bind all accept them without error and all fail to change this. This is not a bug you can fix; it is what lexical binding means.

Key takeaways

  • call sets this by temporarily making the function a method of the target object.
  • Use a Symbol key so you cannot collide with existing properties.
  • Use try/finally so a throwing function does not leave state behind.
  • The null-defaulting and primitive-boxing rules are sloppy-mode behaviour and cannot be fully reproduced from a module.

Goal: Implement Function.prototype.call with correct this handling, primitive boxing, and collision-free temporary storage.

Frequently asked questions

Why is implementing call a common interview question?
Because writing it requires you to explain what `this` actually is rather than describe it. The implementation is only a few lines, but every line depends on understanding how a function's calling context is decided.
How does a call polyfill set this?
By making the function a temporary property of the context object and invoking it as a method, so the normal method-call rule supplies `this`. The property is then removed. Using a Symbol for the temporary key avoids overwriting an existing property, which is the detail that separates a careful implementation from one that quietly corrupts the caller's object.
What is the difference between call, apply and bind?
`call` and `apply` both invoke the function immediately and differ only in how arguments are passed — individually for `call`, as an array for `apply`. `bind` invokes nothing; it returns a new function with the context permanently attached, to be called later.
What happens when you pass null as the context?
In non-strict mode the context falls back to the global object, and primitives get wrapped in their object form. Interviewers ask about this to see whether you know the coercion rules exist, so it is worth handling the null case explicitly and saying why.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

Function.prototype.apply Polyfill

Implement Function.prototype.apply from scratch — identical to call except arguments arrive as a single array.

JavaScript · Functions · PolyfillsPratik Rai ·

JavaScript

Function.prototype.bind Polyfill

Implement Function.prototype.bind from scratch, including partial application and the new-operator edge case that almost nobody gets right.

JavaScript · Functions · PolyfillsPratik Rai ·

JavaScript

Promise.all Polyfill

Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.

JavaScript · Promises · Async/AwaitPratik Rai ·