#Functions#Polyfill

Function.prototype.apply Polyfill

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

By Pratik RaiMedium

Function.prototype.apply invokes a function with an explicit this and an array of arguments. It is call's sibling, differing only in how arguments arrive — but that one difference is what made it indispensable for two decades of JavaScript, and it carries a hard engine limit that still bites people today.

Why implement it yourself

apply is the shortest of the three context methods to write, so on its own it is a warm-up. Its value is in what it explains: before the spread operator existed, apply was the only way to pass a runtime-length list of arguments to a function. Nearly every clever pre-ES6 idiom you will find in old code — Math.max.apply(null, arr), [].concat.apply([], arrays), array-like conversion — exists because of it.

What the native method does

JSfile.javascript
1function introduce(greeting, punctuation) { 2 return `${greeting}, I am ${this.name}${punctuation}`; 3} 4 5introduce.apply({ name: 'Ada' }, ['Hello', '!']); // "Hello, I am Ada!"

The specification requires:

  • A TypeError if the target is not callable.
  • null or undefined for thisArg defaults to the global object in sloppy mode; primitives are boxed.
  • The second argument may be null or undefined, meaning "no arguments" — it is optional in a way call's rest arguments never need to be.
  • Any array-like is accepted, not just a real array. {0: 'a', length: 1} works.

That third point is the one people forget. fn.apply(obj) with no second argument is legal and calls fn with zero arguments.

The implementation

The mechanism is identical to call — temporarily attach the function to the context object so that a method call sets this, then clean up:

JSfile.javascript
1Function.prototype.myApply = function (thisArg, argArray) { 2 if (typeof this !== 'function') { 3 throw new TypeError('myApply must be called on a function'); 4 } 5 6 const context = 7 thisArg === null || thisArg === undefined ? globalThis : Object(thisArg); 8 9 // `apply` accepts null/undefined to mean "no arguments". 10 const args = 11 argArray === null || argArray === undefined ? [] : Array.from(argArray); 12 13 const key = Symbol('fn'); 14 context[key] = this; 15 16 try { 17 return context[key](...args); 18 } finally { 19 delete context[key]; 20 } 21};

Array.from handles the array-like requirement in one step and also accepts iterables, which is slightly more permissive than the spec but rarely a problem in practice.

The Symbol key and the try/finally matter for the same reasons they do in call: a string key can collide with a property the caller already owns, and a function that throws would otherwise leave the temporary property attached permanently.

The argument limit

This is apply's one genuinely dangerous property. Arguments are placed on the stack, and every engine caps how many it will accept — roughly 65,535 in older engines, higher but still finite in modern V8. Exceed it and you get RangeError: Maximum call stack size exceeded.

JSfile.javascript
1const big = new Array(500000).fill(1); 2Math.max.apply(null, big); // RangeError 3Math.max(...big); // RangeError — spread has the same limit

The spread operator does not save you; it compiles to the same mechanism. For large arrays, use a reducer or chunk the input:

JSfile.javascript
1big.reduce((max, n) => (n > max ? n : max), -Infinity); // safe at any size

This trips people up precisely because Math.max.apply(null, arr) is the textbook example of apply, and it works fine right up until your array gets big enough in production.

Idioms worth recognising

Math.max.apply(null, arr) — find the maximum of an array. Superseded by Math.max(...arr), with the same size caveat.

[].concat.apply([], arrays) — flatten one level. Superseded by arr.flat().

Array.prototype.slice.call(arguments) — convert an array-like to a real array. Superseded by Array.from and rest parameters.

Recognising these matters when reading older code, and each one is a small lesson in why the modern syntax exists.

Common follow-ups

"When would you use apply over call today?" Honestly, rarely — spread covers most cases more readably. It still wins when you already have an array in hand and want to avoid the spread's allocation, and when forwarding arguments verbatim in code that cannot use rest parameters.

"What is the difference between call, apply and bind?" call and apply invoke immediately and differ only in argument shape; bind returns a new function and invokes nothing. The mnemonic: apply takes an array, call takes a comma-separated list.

Key takeaways

  • apply is call with arguments passed as an array-like.
  • The second argument is optional — null and undefined both mean "no arguments".
  • Use a Symbol key and try/finally, same as call.
  • Argument count is capped by the engine stack; spread does not raise the ceiling.
  • Most historical apply idioms have cleaner modern replacements.

Goal: Implement myApply. Reuse your call logic — the only new piece is handling the args array.

Frequently asked questions

What does Function.prototype.apply actually do?
It calls a function with an explicit `this` value and an array of arguments. `apply` and `call` differ only in how the arguments arrive — `apply` takes an array, `call` takes them individually — which is why implementing one usually means you can implement the other in a line.
Why does the implementation attach the function to the context object?
Because the simplest way to set `this` is to make the function a method of the object and call it there: `context.fn(...args)` binds `this` to `context` by the ordinary call rules. That is also why you should use a `Symbol` as the key rather than a string — a plain name like `fn` can collide with a property the object already had, and you would overwrite somebody's data for the duration of the call.
What happens when the context is null, undefined or a primitive?
In non-strict mode `null` and `undefined` fall back to `globalThis`, and primitives are boxed into their object wrappers — `apply.call(fn, 5)` gives you a `Number` object as `this`, not the number. Interviewers ask specifically because it shows whether you know the spec's coercion step exists rather than assuming the value is passed through untouched.
What are the follow-ups after apply?
Implementing `call` and `bind`, and explaining why `bind` is harder: it returns a new function rather than calling one, has to support partial application, and must still behave correctly when the result is used with `new`.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

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.

JavaScript · Functions · thisPratik 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

Array.prototype.reduce Polyfill

Implement Array.prototype.reduce from scratch, including the tricky no-initial-value path and the empty-array error case.

JavaScript · Arrays · PolyfillsPratik Rai ·