According to the results of the 2018’s StackOverflow survey, JavaScript is the most popular technology. The amount of job offers for JavaScript developers is constantly increasing and with more companies adopting JavaScript as their main language, it’s easy to find good ones. But before you are hired by a company, you have to demonstrate your skills and pass the interview process.
In this article, I’ll discuss 5 JavaScript interview questions that every mid-level developer (or higher) should be able to answer. So, if JavaScript is your favorite language and you’re looking for new opportunities, or you simply want to challenge yourself with a few questions on JavaScript, read on!
Question 1: Scoping
Consider the following code:
function foo() { var a = 1; const b = 2; let c = 3; if (b < 10) { var a = 10; const b = 11; let c = 12; console.log(a, b, c); } console.log(a, b, c); console.log(d, e); var d = 4; const e = 5; } foo();
What will be printed on the console?
Answer
The code above, available on JS Bin, prints:
10 11 12 10 2 3 Uncaught ReferenceError: e is not defined
The purpose of this question is to test the candidate’s understanding of variables scoping. The code employs the var
keyword, and the const
and let
keywords that have been introduced in ECMAScript 2015 (also known as ECMAScript 6).
There are two main concepts the candidate has to grasp to correctly answer this question. The first one is that variables declared using const
and let
, unlike those declared with var
, are hoisted but not initialized. Variables declared using these two keywords are in a temporal dead zone from the start of the block until the declaration is processed. This means that referencing them before they are initialized causes the JavaScript engine to throw a ReferenceError
error. The second concept is that variables declared using const
and let
are block scoped. On the contrary, variables declared with var
are function scoped, so they are available throughout the whole function they are declared in.
Once clarified the two notions needed to answer the question, it’s easy to understand why the value of a
is replaced by the value defined inside the if
, while the same isn’t true for b
and c
. In fact, inside the if
block two different b
and c
variables are created. So, the condition of the if
is verified. The b
inside the condition of the if
is the one declared at the beginning of the function, so its value is 2. This explains why the first console.log()
statement executed is the one inside the if
and why it prints 10 11 12
. For these same reasons, the values printed by the second console.log()
statement are 10 2 3
. Finally, the e
variable is declared using const
, thus it’s hoisted but not initialized. At the time the third console.log()
statement is executed, the e
variable is in a temporal dead zone, which explains the ReferenceError
error thrown.
If you want to learn more about let
, const
, block scope, and the temporal dead zone, I recommend you the following resources:
Question 2: Event delegation
Consider the following HTML snippet representing a tab system:
<div class="tabs"> <ul class="tabs-header"> <li class="active"> <a href="#tab-1">Tab 1</a> </li> <li> <a href="#tab-2">Tab 2</a> </li> <li> <a href="#tab-3">Tab 3</a> </li> </ul> <article class="tab active" id="tab-1"> Content 1 </article> <article class="tab" id="tab-2"> Content 2 </article> <article class="tab" id="tab-3"> Content 3 </article> </div>
Write the JavaScript code needed to show the correct tab when the relevant tab header is selected. Both the tab and its header are considered visible if they possess the active
CSS class name.
Answer
A possible implementation is listed below:
const tabs = Array.from(document.querySelectorAll('.tab')); const tabsHeaders = Array.from(document.querySelectorAll('.tabs-header li')); document .querySelector('.tabs') .addEventListener('click', function(event) { // Do not execute the logic if the element selected is not an anchor if (event.target.tagName !== 'A') { return; } event.preventDefault(); // Remove the active class name from the previous active tab header tabsHeaders.forEach(header => header.classList.remove('active')); // Add the active class name to the selected tab header event .target .parentNode .classList .add('active'); // Remove the active class name from the previous active tab tabs.forEach(tab => tab.classList.remove('active')); // Add the active class name to the selected active tab tabs .filter(tab => `#${tab.id}` === event.target.getAttribute('href')) .shift() .classList .add('active'); });
A complete demo of this solution is available on JS Bin.
The aim of this question is to verify the knowledge of a technique called event delegation.
To achieve the goal the candidate could be tempted to add an event listener to every tab header, but this approach would cause a waste of memory. The same result can be achieved more efficiently by employing a technique known as event delegation. Instead of adding an event listener to every tab header, the candidate can add a single event listener to the unordered list (the parent of the tab headers). Then, based on the element on which the click
event is fired, the relevant tab can be shown. The latter is identified by the match between the id
property of the tab and the href
attribute of the selected anchor element.
If you need an introduction to event delegation, I suggest you to read the following tutorials:
bind()
, call()
, and apply()
What’s the difference between bind()
, call()
, and apply()
?
Answer
All these methods belong to the Function
object’s prototype which means that every function possesses these methods. They all allow to change the context of the function, that is the object referred by the this
keyword, on which they operate without changing its logic. Also, they all accept the same first argument, which is the value to use as the context of the function they operate on. Having said that, let’s discuss the differences.
bind()
is different from the two other methods because it returns the modified function and doesn’t execute either the function it operates on or the modified function. It can also be used to preset some of the parameters of the original function. Both call()
and apply()
execute the modified function and return whatever value is returned by the latter. The difference is that, in addition to the first argument discussed before, call()
accepts an argument for each parameter to pass to the modified function while apply()
accepts an array of arguments.
To better understand this difference, let’s see an example that involves the use of the console.log()
function to print on the console the value of two variables: foo
and bar
. All of the calls to console.log()
below prints the same output.
var foo = 1; var bar = 2; var log = console.log.bind(console, foo, bar); log(); console.log.call(console, foo, bar); console.log.apply(console, [foo, bar]);
A live demo of this snippet is shown below but it’s also available on JS Bin.
To learn more about these three methods, you can read the following pages:
- More Control over Function Calls: call(), apply(), and bind()
- Chapter 2: this All Makes Sense Now! of the book this & Object Prototypes
Question 4: Pure functions
Explain what pure functions are and some of their advantages.
Answer
In simple terms, a pure function is a function where the return value is only determined by its input values and that doesn’t have side effects such as the mutation of an object. This means that the function always returns the same result given the same arguments and that it doesn’t depend on a given application state that may change while the software is executed.
An example of a pure function is the test()
function reported below. It declares two parameters, an array of numbers (array
) and a number (max
). The function returns true
if the sum of the numbers of the array is less than the number; false
otherwise.
function test(array, max) { const sum = array.reduce((partial, number) => partial + number); return sum < max; }
As you can see, the returned value of the test()
function is only calculated on the basis of the provided arguments. Other examples of pure functions can be found in the JavaScript language itself. Some examples are Math.sin()
, Math.max()
, and Number.parseInt()
.
To give you a better understanding of what pure functions are, let’s see an example of an impure function:
function sumRandom(number) { return Math.random() + number; }
The sumRandom()
function defined above is impure because its returned value depends on the random number calculated inside the function. So, even if we pass the same argument, at every call of the function the result will be different.
The main advantage of pure functions is their testability. Because the return value depends on arguments provided only, you don’t have to assume or mock any state of your software and you can focus on arguments and return values. Pure functions help you in writing predictable and deterministic code, which is easier to test.
Another advantage is that pure functions can be executed in parallel because they don’t have side effects, thus there is no chance they conflict with each other. Pure functions are also usually easier to understand and reuse because they don’t depend on a given state of the system nor they change the state of the application. Finally, results of pure functions can be cached for future reuse because the same input always yields the same output.
If you want to learn more about pure functions I advise you to read these articles:
- Master the JavaScript Interview: What is a Pure Function?
- An Introduction to Reasonably Pure Functional Programming
Question 5: Promise
s
A company offers a service that generates random numbers between 1 and 1000, one number at every request. Write a product()
function that accepts the amount of random numbers to generate and returns their product. You can assume the presence in the code base of a getRandomNumber()
function, reported below, that returns a Promise
which will resolve with the generated random number.
function getRandomNumber() { const min = 1; const max = 1000; const randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; return Promise.resolve(randomNumber); }
Answer
The goal of this question is to test the candidate’s knowledge of the Promise
object and its methods in JavaScript. It also verifies that the candidate understands the challenge of synchronizing several asynchronous calls to obtain the final result.
As always there isn’t one solution to a problem but the presented one, also available on JS Bin, is a good trade-off between conciseness, clever techniques, and readability.
function product(numbers) { const randomNumbers = Array .from(Array(numbers)) .map(() => getRandomNumber()); return Promise .all(randomNumbers) .then(values => values.reduce((prod, value) => prod * value, 1)); }
The code is made of two sections. In the first one you can see that an array of numbers
length is created. The array is initially filled with undefined
values (Array.from(Array(numbers))
). Then, by using map()
, a new array of the same length containing Promise
objects is created. These objects will eventually resolve with the generated random number.
Once created the array of Promise
s, the code waits until all of them are resolved to calculate the product. The synchronization is done with the use of Promise.all()
.
If you have never used the Promise
object and you need a primer on the topic, I suggest you to read the following resources:
Conclusions
In this article I’ve discussed some JavaScript interview questions that you could be asked for a position as a JavaScript developer. Even if these are not the questions that you’ll actually be asked, they should have given you a good refresh of some important concepts. I hope you had fun testing your knowledge and that you learned a thing or two you didn’t know.