Frontend interview questions: Bind, apply, and call



Implementing bind
, apply
, or call
is probably one of the most popular questions in an interview for a JavaScript developer position. I’ve personally been asked these questions in one way or another maybe dozens of times throughout my career. And, in my opinion, there’s a reason for that. Execution context, functional scope, and partial application are certainly some of the most fundamental concepts in JavaScript, and every developer who writes code for the web should be familiar with them. These questions cover all of these concepts.
Well, the way companies conduct frontend coding interviews has changed over the last decade. They’d probably want you to focus on more practical things, such as async programming (using Promises or async
/ await
's) or more UI-focused problems, like implementing a widget with the framework of your choice (or even in vanilla JavaScript). However, I still think I absolutely must start this series with bind
, apply
, and call
. This question is still a perfect warm-up: it’s simple, but at the same time, it can give the interviewer valuable insights about the candidate and spark a more detailed conversation about the underlying concepts.
In this article, I’d like to discuss the classic implementations of all three methods, as well as some alternative ones that aren’t as popular but still cover interesting topics and showcase a deep knowledge of JavaScript.
Alright, let’s jump into the code and implement bind
first!
Bind
Bind was added to JavaScript in ECMAScript 5 (ES5) back in 2009 and it is a JavaScript utility method that allows you to bind a function or method to a specific execution context. In other words, it lets you control what the this
keyword refers to inside the function body. This can be useful, for instance, when you need to define an event handler and pass it somewhere.
Let’s say you define a method on an object and want to use it as the event handler for a button click. If that method refers to this
, you can’t simply pass it as is - because, in that case, it’ll lose its execution context, and this
will end up referring to something else (or nothing at all). Unless, of course, you use an arrow function to define the method, as arrow functions inherit their parent execution context and cannot be bound. To fix this issue, you can use bind
to bind the method to its original context. bind
returns a new function, which can then be passed as the event handler.
But that’s not all — bind
can also be used for partial application. In addition to specifying the execution context, bind
allows you to pass arguments that the original function will be called with. And you don’t necessarily have to pass all of the arguments.
For example, let’s say you define a function that takes three arguments — three numbers — and sums them. Now, you want the first argument to always be a specific number (10 in this example). One way to achieve this is by using bind
and passing 10
as the second argument (the first argument is always the execution context, or this
, while the rest are the arguments that will be passed to the original function when it’s called). The result of this operation will be a new function that takes the remaining two arguments.
For instance, I called the bound function with 3
and 2
and got 15
as the result.
But how can this function be implemented? Let’s start with a classic implementation.
Classic implementation
I want to to start with a classic implementation of this method because it’s probably what an interviewer actually wants from you: implement it real quick, be able to explain the underlying concepts and then move on to more complex questions.
Classic implementation involves using one of 2 other methods we’ll discuss in this video: apply
or call
. The idea is pretty simple: return a new function that calls either of those under the hood. With ES6 and the rest operator, solution for this problem has really become a one-liner, as I mentioned earlier - a perfect warm-up question:
interface Function {
myBind(this: Function, thisArg: any, ...argArray: any[]): Function;
}
Function.prototype.myBind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
return (...otherArgs: any[]) => {
return this.apply(thisArg, argArray.concat(otherArgs));
};
};
If the interviewer wants you to implement this function without a rest operator, it apparently means that they want to hear more about arguments
pseudo-array that is available in every function (with the exception of arrow functions). This arguments
object was used in JavaScript for pretty much the same purpose as the rest operator: to access the function arguments when the actual number of these arguments is unknown. Implementing bind
with arguments
is similar to the rest operator version but there is one caveat - as I said, arguments
is a pseudo array and it doesn’t have access to any of the array methods:
interface Function {
myBind(this: Function, thisArg: any, ...argArray: any[]): Function;
}
Function.prototype.myBind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
return function () {
return this.apply(
thisArg,
Array.prototype.concat.call(arguments, otherArgs)
);
};
};
These are 2 variations of the classical implementation and 95% of the time this is what the interviewer wants you to write. So make sure you understand this code very well before proceeding to alternative implementations.
Alternative implementations
Using reflection
call
or apply
can be shadowed
interface Function {
myBind(this: Function, thisArg: any, ...argArray: any[]): Function;
}
Function.prototype.myBind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
return (...otherArgs: any[]) => {
return Reflect.apply(this, thisArg, argArray.concat(otherArgs));
};
};
or
interface Function {
myBind(this: Function, thisArg: any, ...argArray: any[]): Function;
}
Function.prototype.myBind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
const context = this;
// Check that context is a function
return function (...otherArgs: any[]) {
return Reflect.apply(context, thisArg, argArray.concat(otherArgs));
};
};
Using Symbol
Idea quite Similar to Reflect version: we need to find a way that it’s not possible to shadow the original method. I personally wouldn’t expect a candidate to come up with this solution during the interview but again, it can provide a bunch of good signals to the interviewer: understanding of Symbols, how prototype inheritance works, how to define a property on an object with a property descriptor, etc.
So, in order to implement bind
with this approach, we need to a wrapper object that’s inherited from the new context passed to our bind
implementation. And then we need to make the original function, a method of this wrapper object. But how can we make sure that the method name is unique and can’t be shadowed by the consumer? The right answer here is to use Symbol
as symbols are always unique, even if we pass the same key to the constructor.
Then we can leverage the symbol’s uniqueness and use it as a method name. The wrapper object has now been created, the method added and now we just need to call it. Let’s create a function that’s going to be a result function and let’s call the method we’ve just created inside this function
Function.prototype.myBind = function (
this: Function,
thisArg: any,
...argArray: any[]
) {
const sym = Symbol();
const wrapperObj = Object(thisArg);
Object.defineProperty(wrapperObj, sym, {
enumerable: false,
value: this,
});
return function (...args: any[]) {
return wrapperObj[sym](...argArray, ...args);
};
};
I think that’s more than enough for bind
, so let’s move on to the apply
and call
.
Apply and call
Apply
and call
were added to JavaScript a decade earlier in ECMAScript 3 (ES3) and they are the function utility methods that do the same thing but in a slightly different way. Both methods invoke the original function with a new context and a specified list of parameters and return the result of this invocation. However, apply takes function arguments as an array, whereas call
takes them as separate parameters. Everything else is identical.
Classic implementation
Classic implementation of these functions asked in interviews involves using bind
method that we implemented in the first part of this video.
interface Function {
myApply(this: any, thisArg: any, argArray?: any[]): any;
}
Function.prototype.myApply = function (thisArg, args: any[]) {
return this.bind(thisArg)(...args);
};
Function.prototype.myCall = function (thisArg, ...args: any[]) {
return this.bind(thisArg)(...args);
};
As I mentioned earlier, these two functions are almost identical, so alternatively we can use one to implement another
Function.prototype.myApply = function (thisArg, args: any = []) {
return this.call(thisArg, ...args);
};
Function.prototype.myCall = function (thisArg, ...args: any[]) {
return this.apply(thisArg, args);
};
Alternative implementations
Again, these methods are similar to bind
and we can use both Symbol
and Reflect
approaches to implement apply
and call