Implement Function.prototype.apply under the name myApply. It does the same thing as Function.prototype.call — invoke the function with a given this value — except the arguments arrive as a single array (or array-like) rather than spread out one by one.
You'll attach the method to Function.prototype so that any function can call fn.myApply(thisArg, argsArray). The function must execute synchronously, with this rebound for the duration of that one call, and return whatever the original function returns.
// Defined on Function.prototype, so available on every function:
// fn.myApply(thisArg, argsArray)
//
// thisArg — the value to bind as `this` inside fn
// argsArray — an array or array-like; its elements become fn's positional args
// null/undefined means "call with zero args"
//
// returns — whatever fn returns
Function.prototype.myApply = function (thisArg, argsArray) { /* ... */ };
Bind this and pass arguments as an array:
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'Ada' };
greet.myApply(user, ['Hello', '!']); // 'Hello, Ada!'
Compare to myCall — same outcome, different argument shape:
greet.myCall(user, 'Hi', '?'); // 'Hi, Ada?'
greet.myApply(user, ['Hi', '?']); // 'Hi, Ada?' (array, not spread)
Borrowing array methods on an array-like (the classic arguments trick):
function sum() {
// `arguments` is array-like, not an array. Borrow Array.prototype.slice
// and apply it with `arguments` as the args list.
const args = Array.prototype.slice.myApply(arguments);
return args.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
argsArray may be null or undefined — treat it as "call with zero args", matching the spec.argsArray may be array-like — anything with a numeric length and indexed properties (arguments, a DOM NodeList, {0: 'a', length: 1}) should work. The spread operator handles this for you when the value is iterable; otherwise read indices up to length.apply, call, or bind — that defeats the exercise. The spread operator (fn(...args)) is allowed and is the natural tool here.arguments ceremony for variadic args — myApply itself takes exactly two named parameters; the array you receive is the argument list to forward.myApply is transparent: whatever fn returns, you return.You'll attach a method to Function.prototype that does what the built-in apply does: invoke this (the function) with a chosen thisArg and an array of arguments.
Function.prototype.call and Function.prototype.apply solve the same problem — call this function, but use a different this — and they differ only in how you hand over the arguments. call takes them spread out (fn.call(obj, a, b, c)); apply takes them in a single array (fn.apply(obj, [a, b, c])). That single difference is huge in practice: any time the argument list is computed at runtime and lives in an array or array-like (like the legacy arguments object), apply is the tool you reach for.
You're rebuilding apply from scratch as myApply. No Function.prototype.apply, no call, no bind — you only have function invocation and the spread operator.
Think of myCall and myApply as twins. They produce the same call at the end — same function, same bound this, same final argument list. They only disagree on how the caller hands over the args: spread across positional parameters, or packed into one array.
Your job inside myApply is twofold: (1) take whatever array-shaped thing the caller gave you and turn it into a list you can spread; (2) make sure this inside the function refers to thisArg, not the global object.
If you're allowed to use the built-in apply, the implementation is one line — but that's the thing you're trying to replace. So the next obvious attempt is to spread argsArray directly:
Function.prototype.myApply = function (thisArg, argsArray) {
// Hope that `this` magically becomes thisArg. (It doesn't.)
return this(...argsArray);
};
Three things break. First, this isn't rebound. this(...argsArray) calls the function, but invokes it as a plain function call — inside the function, this is undefined (strict mode) or the global object (sloppy mode), not thisArg. Second, argsArray might be null/undefined (the spec allows this — it means "call with zero args"); spreading null throws TypeError: object null is not iterable. Third, argsArray might be an array-like such as the legacy arguments object — the spread operator works only on iterables, and pure array-likes like {0: 'a', length: 1} are not iterable.
So we need to (a) bind this, (b) tolerate null/undefined, and (c) handle array-likes.
Function.prototype.myApply = function (thisArg, argsArray) {
const fn = this; // capture the function we were called on — `this` will change once we re-dispatch
// Normalize argsArray into a real array we can spread.
// null/undefined → "no args" per spec. Otherwise read up to `length`,
// which works for both real arrays and array-likes (NodeList, arguments, etc.).
const args = [];
if (argsArray != null) {
const len = argsArray.length >>> 0; // >>> 0 coerces non-numeric length to a safe uint32
for (let i = 0; i < len; i++) args.push(argsArray[i]);
}
// No thisArg → invoke as a plain function call. Spreading `args` (a real
// array we just built) is safe; spreading the raw argsArray would not be.
if (thisArg == null) {
return fn(...args);
}
// The "temporary method" trick: when you call `obj.method(...)`, JavaScript
// automatically binds `this` to `obj` inside `method`. We exploit that by
// hanging fn off thisArg under a unique key, calling it, then removing it.
const ctx = Object(thisArg); // wrap primitives (e.g. a number thisArg) into objects
const key = Symbol('myApply'); // Symbol guarantees no collision with existing keys
ctx[key] = fn;
try {
return ctx[key](...args); // method call → `this` inside fn === ctx (≈ thisArg)
} finally {
delete ctx[key]; // always clean up, even if fn threw
}
};
module.exports = {};
The four shifts from the naive version are worth calling out. const fn = this is non-negotiable — the moment we re-dispatch through ctx[key](...), this flips to refer to ctx, so we must capture the original target first. The index-based loop instead of [...argsArray] is what lets us handle plain array-likes like arguments or {0: 'a', length: 1} that aren't iterable but do have a length. The argsArray == null guard matches the spec's zero-arg behavior. The Symbol key + try/finally make the temp-method assignment safe — Symbols can't collide with anything else on the object, and finally guarantees we don't leave a stray property even if fn throws.
The arg-handling half of the function is just as interesting:
Trace greet.myApply({ name: 'Ada' }, ['Hello', '!']) where greet is:
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
myApply. this is greet (the function the dot-call landed on), so fn = greet. thisArg = { name: 'Ada' }. argsArray = ['Hello', '!'].argsArray != null, so we read len = 2 >>> 0 = 2 and loop. After the loop, args = ['Hello', '!'].thisArg is a non-null object, so we skip the plain-call shortcut.ctx = Object({ name: 'Ada' }) is the same object. key = Symbol('myApply') (a brand-new unique symbol). ctx[key] = greet — ctx is now effectively { name: 'Ada', [Symbol('myApply')]: greet }.ctx[key]('Hello', '!'). Because this is a method call, this inside greet is ctx. So this.name is 'Ada', greeting is 'Hello', punctuation is '!'. The body returns 'Hello, Ada!'.finally block runs delete ctx[key]. The caller's thisArg looks identical to how they handed it in.'Hello, Ada!' propagates back out of myApply.Complexity. O(n) time and O(n) space in the length of argsArray — we copy each element once. The Symbol allocation and the temp-property assign/delete are O(1). For the no-thisArg branch we don't even allocate a Symbol.
this after re-dispatch. Once you've written ctx[key] = this; ctx[key](...), the next this you see in the surrounding code refers to ctx, not the original function. Capture it as const fn = this on the first line of myApply and use the local — otherwise the temp-property assignment hangs the wrong thing on ctx. The bug is silent until you trace it.[...argsArray] instead of an index loop. The spread operator only works on iterables. Real arrays are iterable, so the spread seems fine — until someone passes a plain array-like like {0:'a', length:1}, or arguments in a context where it isn't iterable. Either throws TypeError: argsArray is not iterable. Use an index loop that reads argsArray.length and argsArray[i]; it works for both.argsArray == null guard. The spec explicitly allows fn.apply(thisArg) and fn.apply(thisArg, null) — both mean "call with zero args". If you skip the guard and try to read null.length, you'll throw TypeError: Cannot read properties of null (reading 'length') — and your function is now broken on a perfectly legal call shape.Symbol. obj['__tmpFn'] = fn looks fine until someone passes a thisArg that already has a __tmpFn property — you've quietly overwritten their data. Even with delete, you've lost the original value forever. A Symbol is unique by construction; collisions are impossible.try/finally. If fn throws, you still want ctx[key] cleaned up — otherwise you've permanently glued a function onto someone else's object. try/finally makes the cleanup unconditional.A few capabilities the real Function.prototype.apply has that we've intentionally skipped:
myApply, you can write myBind(thisArg, ...preset) that returns a new function whose later calls forward both preset and the new args. The implementation is a closure that captures thisArg and preset and uses myApply internally on every invocation.new-callable functions. The real spec distinguishes "called as a function" from "called with new". Our myApply only handles the former; the [[Call]] vs [[Construct]] split is a deeper rabbit hole that involves Reflect.construct and new.target.thisArg. If the caller passes an Object.freezed object, our ctx[key] = fn assignment silently fails in sloppy mode and throws in strict mode. A production implementation falls back to creating a fresh wrapper object that delegates to thisArg via prototype — that way you never mutate the caller's object at all.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Implement Function.prototype.apply under the name myApply. It does the same thing as Function.prototype.call — invoke the function with a given this value — except the arguments arrive as a single array (or array-like) rather than spread out one by one.
You'll attach the method to Function.prototype so that any function can call fn.myApply(thisArg, argsArray). The function must execute synchronously, with this rebound for the duration of that one call, and return whatever the original function returns.
// Defined on Function.prototype, so available on every function:
// fn.myApply(thisArg, argsArray)
//
// thisArg — the value to bind as `this` inside fn
// argsArray — an array or array-like; its elements become fn's positional args
// null/undefined means "call with zero args"
//
// returns — whatever fn returns
Function.prototype.myApply = function (thisArg, argsArray) { /* ... */ };
Bind this and pass arguments as an array:
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'Ada' };
greet.myApply(user, ['Hello', '!']); // 'Hello, Ada!'
Compare to myCall — same outcome, different argument shape:
greet.myCall(user, 'Hi', '?'); // 'Hi, Ada?'
greet.myApply(user, ['Hi', '?']); // 'Hi, Ada?' (array, not spread)
Borrowing array methods on an array-like (the classic arguments trick):
function sum() {
// `arguments` is array-like, not an array. Borrow Array.prototype.slice
// and apply it with `arguments` as the args list.
const args = Array.prototype.slice.myApply(arguments);
return args.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
argsArray may be null or undefined — treat it as "call with zero args", matching the spec.argsArray may be array-like — anything with a numeric length and indexed properties (arguments, a DOM NodeList, {0: 'a', length: 1}) should work. The spread operator handles this for you when the value is iterable; otherwise read indices up to length.apply, call, or bind — that defeats the exercise. The spread operator (fn(...args)) is allowed and is the natural tool here.arguments ceremony for variadic args — myApply itself takes exactly two named parameters; the array you receive is the argument list to forward.myApply is transparent: whatever fn returns, you return.You'll attach a method to Function.prototype that does what the built-in apply does: invoke this (the function) with a chosen thisArg and an array of arguments.
Function.prototype.call and Function.prototype.apply solve the same problem — call this function, but use a different this — and they differ only in how you hand over the arguments. call takes them spread out (fn.call(obj, a, b, c)); apply takes them in a single array (fn.apply(obj, [a, b, c])). That single difference is huge in practice: any time the argument list is computed at runtime and lives in an array or array-like (like the legacy arguments object), apply is the tool you reach for.
You're rebuilding apply from scratch as myApply. No Function.prototype.apply, no call, no bind — you only have function invocation and the spread operator.
Think of myCall and myApply as twins. They produce the same call at the end — same function, same bound this, same final argument list. They only disagree on how the caller hands over the args: spread across positional parameters, or packed into one array.
Your job inside myApply is twofold: (1) take whatever array-shaped thing the caller gave you and turn it into a list you can spread; (2) make sure this inside the function refers to thisArg, not the global object.
If you're allowed to use the built-in apply, the implementation is one line — but that's the thing you're trying to replace. So the next obvious attempt is to spread argsArray directly:
Function.prototype.myApply = function (thisArg, argsArray) {
// Hope that `this` magically becomes thisArg. (It doesn't.)
return this(...argsArray);
};
Three things break. First, this isn't rebound. this(...argsArray) calls the function, but invokes it as a plain function call — inside the function, this is undefined (strict mode) or the global object (sloppy mode), not thisArg. Second, argsArray might be null/undefined (the spec allows this — it means "call with zero args"); spreading null throws TypeError: object null is not iterable. Third, argsArray might be an array-like such as the legacy arguments object — the spread operator works only on iterables, and pure array-likes like {0: 'a', length: 1} are not iterable.
So we need to (a) bind this, (b) tolerate null/undefined, and (c) handle array-likes.
Function.prototype.myApply = function (thisArg, argsArray) {
const fn = this; // capture the function we were called on — `this` will change once we re-dispatch
// Normalize argsArray into a real array we can spread.
// null/undefined → "no args" per spec. Otherwise read up to `length`,
// which works for both real arrays and array-likes (NodeList, arguments, etc.).
const args = [];
if (argsArray != null) {
const len = argsArray.length >>> 0; // >>> 0 coerces non-numeric length to a safe uint32
for (let i = 0; i < len; i++) args.push(argsArray[i]);
}
// No thisArg → invoke as a plain function call. Spreading `args` (a real
// array we just built) is safe; spreading the raw argsArray would not be.
if (thisArg == null) {
return fn(...args);
}
// The "temporary method" trick: when you call `obj.method(...)`, JavaScript
// automatically binds `this` to `obj` inside `method`. We exploit that by
// hanging fn off thisArg under a unique key, calling it, then removing it.
const ctx = Object(thisArg); // wrap primitives (e.g. a number thisArg) into objects
const key = Symbol('myApply'); // Symbol guarantees no collision with existing keys
ctx[key] = fn;
try {
return ctx[key](...args); // method call → `this` inside fn === ctx (≈ thisArg)
} finally {
delete ctx[key]; // always clean up, even if fn threw
}
};
module.exports = {};
The four shifts from the naive version are worth calling out. const fn = this is non-negotiable — the moment we re-dispatch through ctx[key](...), this flips to refer to ctx, so we must capture the original target first. The index-based loop instead of [...argsArray] is what lets us handle plain array-likes like arguments or {0: 'a', length: 1} that aren't iterable but do have a length. The argsArray == null guard matches the spec's zero-arg behavior. The Symbol key + try/finally make the temp-method assignment safe — Symbols can't collide with anything else on the object, and finally guarantees we don't leave a stray property even if fn throws.
The arg-handling half of the function is just as interesting:
Trace greet.myApply({ name: 'Ada' }, ['Hello', '!']) where greet is:
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
myApply. this is greet (the function the dot-call landed on), so fn = greet. thisArg = { name: 'Ada' }. argsArray = ['Hello', '!'].argsArray != null, so we read len = 2 >>> 0 = 2 and loop. After the loop, args = ['Hello', '!'].thisArg is a non-null object, so we skip the plain-call shortcut.ctx = Object({ name: 'Ada' }) is the same object. key = Symbol('myApply') (a brand-new unique symbol). ctx[key] = greet — ctx is now effectively { name: 'Ada', [Symbol('myApply')]: greet }.ctx[key]('Hello', '!'). Because this is a method call, this inside greet is ctx. So this.name is 'Ada', greeting is 'Hello', punctuation is '!'. The body returns 'Hello, Ada!'.finally block runs delete ctx[key]. The caller's thisArg looks identical to how they handed it in.'Hello, Ada!' propagates back out of myApply.Complexity. O(n) time and O(n) space in the length of argsArray — we copy each element once. The Symbol allocation and the temp-property assign/delete are O(1). For the no-thisArg branch we don't even allocate a Symbol.
this after re-dispatch. Once you've written ctx[key] = this; ctx[key](...), the next this you see in the surrounding code refers to ctx, not the original function. Capture it as const fn = this on the first line of myApply and use the local — otherwise the temp-property assignment hangs the wrong thing on ctx. The bug is silent until you trace it.[...argsArray] instead of an index loop. The spread operator only works on iterables. Real arrays are iterable, so the spread seems fine — until someone passes a plain array-like like {0:'a', length:1}, or arguments in a context where it isn't iterable. Either throws TypeError: argsArray is not iterable. Use an index loop that reads argsArray.length and argsArray[i]; it works for both.argsArray == null guard. The spec explicitly allows fn.apply(thisArg) and fn.apply(thisArg, null) — both mean "call with zero args". If you skip the guard and try to read null.length, you'll throw TypeError: Cannot read properties of null (reading 'length') — and your function is now broken on a perfectly legal call shape.Symbol. obj['__tmpFn'] = fn looks fine until someone passes a thisArg that already has a __tmpFn property — you've quietly overwritten their data. Even with delete, you've lost the original value forever. A Symbol is unique by construction; collisions are impossible.try/finally. If fn throws, you still want ctx[key] cleaned up — otherwise you've permanently glued a function onto someone else's object. try/finally makes the cleanup unconditional.A few capabilities the real Function.prototype.apply has that we've intentionally skipped:
myApply, you can write myBind(thisArg, ...preset) that returns a new function whose later calls forward both preset and the new args. The implementation is a closure that captures thisArg and preset and uses myApply internally on every invocation.new-callable functions. The real spec distinguishes "called as a function" from "called with new". Our myApply only handles the former; the [[Call]] vs [[Construct]] split is a deeper rabbit hole that involves Reflect.construct and new.target.thisArg. If the caller passes an Object.freezed object, our ctx[key] = fn assignment silently fails in sloppy mode and throws in strict mode. A production implementation falls back to creating a fresh wrapper object that delegates to thisArg via prototype — that way you never mutate the caller's object at all.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.