MediumMetaUberAmazonPegasystems

apply( ) polyfill

Prompt

Implement a polyfill for the JavaScript .apply() method. Add a myApply method to Function.prototype that works exactly like the native .apply(), without using .call(), .apply(), or .bind().

Playground

Hint 1

.apply() is almost identical to .call(). The only difference is how arguments are passed: .call() takes them individually, .apply() takes them as an array. If you've solved the .call() polyfill, the approach is the same.

Hint 2

Temporarily attach the function as a method on the context object (using a Symbol for a unique key), call it with the spread operator to unpack the arguments array, then clean up.

Solution

Explanation

If you've already solved the call() polyfill, you're 90% done. .apply() does the exact same thing as .call(), with one small difference in how you pass arguments:

// call: arguments passed one by one
greet.call(person, 'Hello', '!');

// apply: arguments passed as an array
greet.apply(person, ['Hello', '!']);

That's it. Same this binding, same immediate execution, same return value. The only change is the function signature: instead of (context, ...args) with rest parameters, .apply() takes (context, argsArray) where the second parameter is a single array.

The implementation

The technique is identical to .call(). We temporarily attach the function to the context object using a Symbol key, call it as a method (so this points to the context), then clean up.

Function.prototype.myApply = function (context, argsArray) {
context = context || globalThis;
argsArray = argsArray || [];

const fnKey = Symbol();
context[fnKey] = this;

const result = context[fnKey](...argsArray);

delete context[fnKey];

return result;
};

The extra line argsArray = argsArray || [] handles the case where someone calls .myApply(context) without passing an arguments array at all. Without this, we'd try to spread undefined and get an error.

When would you use apply over call?

In modern JavaScript, it honestly doesn't matter much because you can spread an array into .call():

const args = [1, 2, 3];
fn.call(context, ...args); // works fine
fn.apply(context, args); // same result

But .apply() was essential before the spread operator existed (pre-ES6). A classic example was finding the max of an array:

// Before ES6 - needed apply
Math.max.apply(null, [1, 5, 3]); // 5

// After ES6 - spread works
Math.max(...[1, 5, 3]); // 5

You'll still see .apply() in older codebases and interview questions.

All three polyfills (.call(), .apply(), .bind()) use the same underlying trick: temporarily attaching a function to an object to control this. .call() and .apply() execute immediately, while .bind() wraps this trick inside a returned function for later execution.