Function.prototype.apply Polyfill
Implement Function.prototype.apply from scratch — identical to call except arguments arrive as a single array.
JavaScript · Functions · Polyfills — Pratik Rai ·
Implement a Function.prototype.call polyfill from scratch. Master JavaScript this binding, execution context, primitive boxing, and safe property assignment techniques.
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.
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.
call looks simple from the outside:
JSfile.javascript1function 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:
TypeError if the target is not callable.thisArg of null or undefined is replaced with the global object.thisArg is coerced to its object wrapper — 3 becomes a Number object.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 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.javascript1Function.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.
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.
"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.
call sets this by temporarily making the function a method of the target object.Symbol key so you cannot collide with existing properties.try/finally so a throwing function does not leave state behind.Goal: Implement Function.prototype.call with correct this handling, primitive boxing, and collision-free temporary storage.
Continue learning with these related challenges
Implement Function.prototype.apply from scratch — identical to call except arguments arrive as a single array.
JavaScript · Functions · Polyfills — Pratik Rai ·
Implement Function.prototype.bind from scratch, including partial application and the new-operator edge case that almost nobody gets right.
JavaScript · Functions · Polyfills — Pratik Rai ·
Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.
JavaScript · Promises · Async/Await — Pratik Rai ·
Implement Function.prototype.apply from scratch — identical to call except arguments arrive as a single array.
JavaScript · Functions · Polyfills
Pratik Rai ·
Implement Function.prototype.bind from scratch, including partial application and the new-operator edge case that almost nobody gets right.
JavaScript · Functions · Polyfills
Pratik Rai ·
Implement your own Promise.all polyfill to understand how concurrent Promise handling works under the hood.
JavaScript · Promises · Async/Await
Pratik Rai ·