1. What are logical operators in JavaScript?
Logical operators allow developers to compare variables and perform tasks based on the result of the comparison. As a hiring manager, you’d ask this question to gauge the candidate’s familiarity with the language and its fundamental features. Your candidate should be able to explain each logical operator and their behavior – stepping through each operand and computing its output.
There are four logical operators in JavaScript:
||
– OR&&
– AND!
– NOT??
– Nullish Coalescing (see next question)
OR
The “OR” operator is represented by two vertical lines (||
). In JavaScript, the “OR” operator evaluates the values from left to right and returns the first truthy value. If none of the values are truthy, the “OR” operator will return the last operand.
let x = 'Hello' || false; // x is equal to 'Hello' (first truthy value)
let y = false || 'Yes' || 1; // y is equal to 'Yes' (first truthy value)
let z = false || undefined || 0; // since all are false, z is equal to 0 (the last value)
AND
The “AND” operator is represented by two ampersands (&&
). In JavaScript, the “AND” operator evaluates the values from left to right and returns the first falsy value. If all the operands are true, the “AND” operator will return the last operand.
let x = 'Hello' && false; // x is equal to 'false' (first falsy value)
let y = 0 && 'Yes' && true; // y is equal to 0 (first falsy value)
let z = true && 'Hello' && 10; // since all are truthy, z is equal to 10 (the last value)
NOT
The “NOT” operator is represented by an exclamation mark (!
). the “NOT” operator accepts a single argument and returns the inverse of its boolean value. The argument is first converted into a boolean (true
or false
). The argument’s boolean value is then inverted and returned (true
becomes false
and vice versa).
let x = !false; // x is equal to true
let y = !('Hello'); // y is equal to false ('Hello' is truthy)
2. What is the nullish coalescing operator in JavaScript?
Nullish coalescing is an addition to JavaScript that helps provide a nicer and more concise syntax for getting the first “defined” value. The candidate should be able to explain what nullish coalescing is at a high-level and how to use the operator when asked this JS interview question.
Nullish coalescing is a JavaScript logical operator represented by two question marks (??
). Nullish coalescing is an operator that returns the first “defined” value. “defined” here refers to an expression whose value is neither null
nor undefined
.
Let’s look at how the operator works.
a ?? b
The output of the code above is as follows:
- if a is defined, the value of a is returned
- if a isn’t defined, the value of b is returned
Let’s look at a few examples of this operator when given a different set of arguments.
let undefinedUser;
console.log(undefinedUser ?? 'Anonymous'); // will print 'Anonymous'
let definedUser = 'Ryan';
console.log(definedUser ?? 'Anonymouse') // will print 'Ryan'
3. What is the difference between == and === operators?
JavaScript has two ways to test equality. Understanding the subtle difference between the two methods is important to prevent misusing each method. The candidate should be able to explain the differences and demonstrate a basic understanding of each method’s usage.
Both double-equals (==
) and triple-equals (===
) are comparison operators meant to compare the equality of two values.
Double-equals only compares the value of the element. Double-equals does type coercion, where the type of the variables is converted to a common type before checking their values. In short, the double-equals operator will return true
for any two variables with the same value regardless of their type.
Triple-equals on the other hand, check for strict equality – both the value and type of the element being compared. Both the value and type of being compared has to match to satisfy the triple-equal operator
let x = 1; // number 1
let y = '1'; // string 1
if (x == y) {
// true! Both x and y's values are equal
}
if (x === y) {
// false! Although both x and y have the same value, x is a number whereas y is a string
}
4. What is a spread operator?
The spread operator is a feature from ES6 to help unpack an element. Candidates being asked this interview question on JavaScript should be able to demonstrate an understanding of how the spread operator expands an element – being able to come up with the output of a spread operator.
Spread operator allows iterables such as arrays, objects, and strings to be expanded into single arguments. The spread operator is denoted by three dots (...
) followed by the variable to be expanded.
Let’s look at an example where we combine two arrays using the spread operator. Below we have a male
and female
array containing a few strings each. The combined
array combines the expanded male
and female
array, creating a single array with the contents from both male
and female
.
const male = ['Mike', 'Alex', 'Bob'];
const female = ['Sam', 'Maggie'];
const combined = [...male, ...female];
console.log(combined); // will print ['Mike', 'Alex', 'Bob', 'Sam', 'Maggie']
5. Explain loops in JavaScript.
We often require repeat actions. Loops are a way to execute the same code multiple times. The candidate should be able to explain how to use loops in JavaScript. An ideal answer should include the pros and cons of each looping method and its respective applications.
There are two main ways to create loops in JavaScript – while
and for
. Both methods consist of a condition to stop the loop and a “loop body”, the code that will be executed multiple times.
while
loop
while
loops are typically used when the “loop body” needs to be repeated an unknown number of times until the condition is met.
The code snippet below shows a simple while loop that prints the value of i
on every iteration and stops when i
is equal to 3.
let i = 0;
while (i < 3) {
console.log(i); // will print 0, 1, and 2
i++;
}
for
loop
A for
loop, on the other hand, is better suited for executing the “loop body” a fixed number of times.
The same loop in the previous code snippet can be re-written using a for
loop this way:
for (let i = 0; i < 3; i++) {
console.log(i); // will print 0, 1, and 2
}
6. Explain the “this” keyword.
This keyword is widely used in JavaScript applications. It behaves differently compared to other languages such as Java and Python. The candidate should have a thorough understanding of how this keyword works and how it relates to its context.
The this
keyword behaves differently depending on the caller’s context. Let’s look at a few contexts and what the this
keyword refers to in each one
Global context
Global context refers to anything outside of any function – global object. this
refers to the window
object in web browsers and global
object in Node.js applications.
If you assign a property to the this
object in a web browser, JavaScript will add that property to the window
object.
// in web browsers
this.name = 'Adam';
console.log(window.name) // will print 'Adam' in your console
Function context
Functions can be invoked in four different ways.
- Function invocation
- Method invocation
- Constructor invocation
- Indirect invocation
Each of the invocations results in a different this
behavior.
Function invocation
Depending on whether you are using “strict mode” or not, the this
keyword refers to different values.
By default, the this
keyword refers to the window
or global
object depending on where you are running the application.
// in web browsers
function callMe() {
if (this === window) {
// true!
}
}
In “strict mode”, JavaScript sets the this
keyword to undefined
.
"use strict"
function callMe() {
if (this === window) {
// false!
}
if (this === undefined) {
// true!
}
}
Method invocation
When you call a method of an object (getName
in the example below), the this
keyword is set to the object that owns the method (user
in the example below).
let user = {
name: 'Bob',
getName: function() {
return this.name;
}
}
console.log(user.getName()); // will print 'Bob' in your console
Constructor invocation
Constructor invocation is when the new
keyword is used to create a new instance of a function object.
The new User('Bob')
is a constructor invocation of the User
function where a new instance of the User
function is created. The this
keyword in this case refers to the newly created object.
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name;
}
let user = new User('Bob');
console.log(user.getName()); // will print 'Bob' in your console
Indirect invocation
Indirect invocation is when the callee of the function uses the call
or apply
keyword to call a function. Both these methods allow passing in the this
value (bob
and adam
in the example below) as a parameter.
function sayHello(greeting) {
console.log(`${gretting} ${this.name}`);
}
let bob = {
name: 'Bob'
};
let adam = {
name: 'Adam'
};
sayHello.call(bob, "Hello"); // will print 'Hello Bob' in your console
sayHello.call(adam, "Hi"); // will print 'Hi Adam in your console
The apply
keyword is identical to the call
keyword above. Instead of accepting a single value as the second parameter, the apply
keyword expects an array of values.
sayHello.call(bob, ["Hello"]); // will print 'Hello Bob' in your console
sayHello.call(adam, ["Hi"]); // will print 'Hi Adam in your console
7. What are the differences between call, apply, and bind?
JavaScript has multiple ways to indirectly invoke a function. Your candidate needs to understand the differences between each and their use cases. You, as the candidate, should be able to explain not only their differences conceptually but also their use case and the reason behind it.
call
, apply
, and bind
are different methods to tie a function to an object and call the function within the specified context.
call
The call
method invokes the function with the specified context – the function is called as if it’s part of the object.
The function sayHello
in the example below references this.name
which is part of the user
object (out of the scope of the sayHello
function). We can use the call
function and pass in the user
object as the first argument to tie the sayHello
function and the user
object momentarily, giving it access to the this.name
property.
let user = { name: 'Bill' };
function sayHello(greeting){
console.log(`${greeting} ${this.name}`)
}
sayHello('Hello'); // will print 'Hello'
sayHello.call(user, 'Hello'); // will print 'Hello Bill'
apply
The apply
method is identical to the call
method with the difference being in how each method accepts their arguments. The call
method accepts an argument list, whereas the apply
method accepts an array of arguments.
Using the same example as above, we can convert the call
method to apply
by wrapping the function’s arguments (excluding the context – user
) in an array before passing it to apply
method.
let user = { name: 'Bill' };
function sayHello(greeting){
console.log(`${greeting} ${this.name}`)
}
sayHello.apply(user, ['Hello']); // will print 'Hello Bill'
bind
Unlike call
and apply
, the bind
method doesn’t execute the function immediately. Instead, it returns a function that is tied to the object that can be executed later.
Let’s update the example again to use the bind
method. We’ll first bind the sayHello
function to the user
object and assign it to a new variable (helloBill
). We can then invoke that function calling it as you would a regular function.
let user = { name: 'Bill' };
function sayHello(greeting){
console.log(`${greeting} ${this.name}`)
}
let helloBill = sayHello.bind(user);
helloBill('Hello'); // will print 'Hello Bill'
8. What are anonymous functions in JavaScript?
Anonymous functions serve numerous purposes in JavaScript. You might ask standard JavaScript interview questions like this one to gauge your candidates’ knowledge of functions in JavaScript and the various ways a function can be created and used. The candidate should be able to explain the difference between anonymous functions and other types of functions and what they are commonly used for
An anonymous function is a function that does not have any name associated with it. We usually use the function
keyword followed by the function’s name to define a function in JavaScript. Anonymous functions omit the function name, making it not accessible after its creation.
An anonymous function can only be accessed by a variable. The anonymous nature of the function makes it great for passing in functions as arguments to other functions (higher-order functions) and functions that are invoked immediately upon initialization.
The following snippet is an example of an anonymous function that is assigned to a variable (sayHello
). The function can then be called by calling the variable name (sayHello
) with the required arguments.
let sayHello = function (name) {
console.log(`Hello ${name}`);
};
sayHello('Chris'); // will print 'Hello Chris'
9. What is hoisting in JavaScript?
Hoisting allows functions to be used safely before they are declared. This question will test the candidate’s familiarity with the JavaScript language and how classes, functions, and variables are interpreted by JavaScript. A basic understanding of hoisting can prevent unexpected errors caused by an incorrect order of declaration, initialization, and reference of a property. You may get other JavaScript hoisting interview questions, so study up!
Hoisting refers to the process where the interpreter moves the declaration of classes, functions, or variables to the top of their scope, before their execution.
Hoisting allows developers to reference variables, functions, and classes before they are declared. Without hoisting, the order of the example below would need to be reversed, with the function declaration first, followed by the caller.
sayHello("Sam");
function sayHello(name) {
console.log(`Hello ${name}`);
}
However, JavaScript only hoists its declarations, not their initializations. Referencing a variable before its initialization would return the variable’s default value (undefined
for variables declared using the var
keyword).
console.log(name); // will print 'undefined' from hoisted var declaration below
var name; // declaration
name = 'Mike'; // initialization
console.log(name); // will print 'Mike' after the previous line (initialization) is executed
10. What is a callback function in JavaScript?
JavaScript runs code sequentially from the top-down. However, sometimes, we need code to run after something has happened (i.e. asynchronous operations). Callback functions are a way to make sure a function runs only after the set of operations is completed. A candidate should be able to explain both how callback functions work and how it relates to asynchronous programming.
A callback function is a function passed into another function as an argument. The callback function is then invoked inside the callee to complete an action.
The example below shows how the callback function is passed into and executed by another function. The last line (greetPerson(sayHello)
) passes the sayHello
function to the greetPerson
function. greetPerson
then executes the sayHello
function by calling the callback
variable, passing in the name
value returned by the prompt
function.
function sayHello(name) {
console.log('Hello ' + name);
}
function greetPerson(callback) {
let name = prompt('Name'); // displays a prompt to the user to submit a name
callback(name);
}
greetPerson(sayHello);
11. What are Promises in JavaScript?
Promises are an effective way to handle asynchronous operations in JavaScript. A candidate should be able to demonstrate a high-level understanding of Promises and how they handle asynchronous operations. An ideal answer would include the tradeoffs of using Promises and how they compare to callbacks and events.
A Promise is a proxy for a value not necessarily known when the promise is created. A promise is a way to handle asynchronous operations in JavaScript. You can think of Promises as an alternative to callbacks and events.
Promises are ideal for handling multiple asynchronous operations, providing a better flow of control definition and error handling.
Let’s look at an example of a Promise that waits for a setTimeout
to complete:
let timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise completed!'); // resolves the promise after 1 second
}, 1000);
});
timeoutPromise.then((result) => {
console.log(result); // will print 'Promise completed!' after 1 second (when the Promise completes)
});
12. What are the different states of a Promise?
Understanding the different states of a promise is important when dealing with promises to avoid unwanted side effects. You might ask this question to gain insight into the candidate’s familiarity with promises beyond the high-level concept.
Because of the asynchronous nature of Promises, a Promise has four states:
- Pending – Promise’s initial state, waiting for the operation to complete
- Fulfilled – Promise’s operation was completed successfully
- Rejected – Promise’s operation failed
- Settled – Promise is either fulfilled or rejected
13. What is Promise chaining?
Promise chaining is a common requirement when working with multiple Promises that depend on each other. A candidate should ideally be able to explain both what promise chaining is and how it is done in JavaScript.
One of the benefits of Promises is its chaining ability. A Promise can be chained using the then
and catch
functions. The then
function will be called when the Promise completes successfully (fulfilled) whereas the catch
function will be called when the Promise failed (rejected).
Each then
and catch
block can contain more Promises and be further chained, providing you with granular control over how each asynchronous operation should be executed.
Let’s look at an example of chaining two Promises that waits for one second between each execution.
new Promise((resolve) => {
setTimeout(() => resolve(1), 1000);
}).then((result) => {
console.log(result); // will print '1' after 1 second
return new Promise((resolve) => {
setTimeout(() => {
resolve(2) // modify the value being resolved
}, 1000)
})
}).then((result) => {
console.log(result); // will print '2' after another 1 second
return result;
})
The first Promise in the code snippet above waits for one second before returning a result of 1
. The code then goes to the then
block where it executes the second Promise, waiting for another second before returning a result of 2
.
14. What is Promise.all?
JavaScript interview questions like this one might be asked as a follow-up to the Promise chaining question. JavaScript provides several utility functions that help with chaining Promises – Promise.all
being one of them. A candidate should be able to describe the function of this type of Promise and also how it alters the flow of the asynchronous functions.
Promise.all
is a type of Promise that accepts an array of Promises and waits for each Promise to resolve. Promise.all
resolves once each of the Promise inputs resolves, emitting an array of results in the then
block. A rejected Promise in the input will cause the Promise.all
Promise to also get rejected.
The example below shows how the Promise.all
function is used to execute two Promises – Promise1
and Promise2
, with a then
block to capture the results of each Promise and a catch
block to handle any errors.
Promise.all([Promise1, Promise2]).then(
([result1, result2]) => {
// result1 contains the result of Promise1
// result2 contains the result of Promise2
}).catch((error) => {
// when either Promise1 or Promise2 gets rejected
});
15. Explain async/await in JavaScript.
Async and await are special syntax to work with Promises. In addition to the “what”, as an interviewer, you might also want to look for practical examples of async and await usages and how it differs from the Promise then
syntax.
The async
keyword is placed before a function to denote that the function is asynchronous and can be used as a Promise.
The await
keyword, on the other hand, tells JavaScript to wait for the async operation to complete before proceeding to the next task in the function. The await
keyword can only be used in an async
function.
Line 6 in the code snippet below pauses the function execution as it waits for the promise to resolve. Once the promise resolves, it will continue the execution, assigning the result of the promise to the result
variable.
async function f() {
let promise = new Promise((resolve) => {
setTimeout(() => resolve('Promise resolved!'), 1000)
});
let result = await promise; // waits for 1 second, until the promise resolves
console.log(result); // will print 'Promise resolved!'
}