Ace the Javascript Interview
Manu Arora / July 03, 2021
21 min read • ––– views
Recently, There is a lot of demand of Javascript developers out there. If you're familiar with React
and Angular
then that will be a plus for you. But if you fail to answer the basics of JavaScript, you will have a tough time clearing the Tech Interview rounds of the companies for which you're appearing.
I've compiled down all the popular concepts and questions related to a Javascript Interview. No matter at what level of understanding you're currently at, this will help you out.
Table of Contents
- Array Methods
- Var, Let, Const
- Hoisting
- == vs ===
- this keyword
- call(), apply() and bind()
- Local Storage / Session Storage
- Timers - setTimeout(), setInterval()
- Polyfills
- Event Loop (The Right Way)
- Promises
- Async / Await
- Closures
- Prototypes
- Debouncing
- Babel and Webpack: Coming Soon
- Server Side Rendering: Coming Soon
Array Methods
Questions around array methods revolve around different array functions that are being used on a day to day basis.
map()
filter()
reduce()
forEach()
find()
map()
map() iterates over an array and returns a new array based on the condition provided.
Example:
const myArray = [1, 2, 3, 4, 5];
const newArray = myArray.map((arrayElement) => arrayElement * 2);
console.log(newArray); // [2, 4, 6, 8, 10]
filter()
Filter does exactly what it says, filters out the elements from an array. Based on the condition, one can filter out the elements from an array.
Example:
let myArray = [1, 2, 3, 4, 5];
let newArray = myArray.filter((arrayElement) => arrayElement > 4);
console.log(newArray); // [5]
reduce()
reduce() is a powerful method which can be used to reduce the values of array to one single element. Values get accumulated over each iteration and at the end, we are left with a single value / element.
Example:
with reduce()
let numbers = [1, 2, 3, 4];
let sum = numbers.reduce((accumulator, current) => accumulator + current);
console.log(sum); // 10
without reduce()
let numbers = [1, 2, 3, 4];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
console.log(sum); // 10
It reduced 3 steps for calculating the sum to just 1 step.
forEach()
This is the simplest to understand method. It simply iterates over the array and performs whichever condition is specified.
Example:
let numbers = [1, 2, 3, 4];
numbers.forEach((number) =>
number % 2 === 0 ? console.log("even") : console.log("odd")
);
Note: forEach()
doesn't return a new array.
find()
find() method takes in a callback function which checks the condition while iterating over every element of the array and returns the first
match.
Example:
let ages = [12, 32, 42, 22, 13];
const checkAge = (age) => {
return age > 18;
};
let result = ages.find(checkAge);
console.log(result); // 32
Note: The difference between indexOf
and find()
is that find()
returns the element, while indexOf
returns the index at which the element is present.
Difference Between map() and forEach()
Var, Let and Const
var
var
var text = "Hey bruh!";
function newFunction() {
var textNew = "hello";
}
console.log(textNew); // error: textNew is not defined
Here, error is generated textNew
is not defined because textNew
is declared within a function, and it is not accessible outside the function scope. While text
can be accessed outside because it is declared outside.
var text = "hey bruh";
var text = "Helloooooooo!";
var text; // valid
let
let
let
is now the preferred way of declaring and initializing variables in JavaScript.
let text = "Hi Manu!";
let length = 10;
if (length > 5) {
let welcome = "Hi Manu, welcome to block scope";
console.log(welcome);
}
console.log(welcome); // Error
let text = "Hi Manu!";
text = "Please redeclare me!"; // error: text has already been declared
But here is a catch,
If the variable is defined in different scopes, there will be no error.
let text = "Hi Manu";
if (true) {
let text = "Hello Paaji!";
console.log(text); // "Hello Paaji!"
}
console.log(text); // "Hi Manu"
const
const
Variables declared with const
maintain a constant value. They share some similarities with let
.
const text = "Hi";
text = "say bye"; // error: Assignment to constant variable.
const text = "Hi";
const text = "say bye"; // error: text has already been declared
const text; // error
Note:
Hoisting
Hoisting is a mechanism of JavaScript where variables and function declarations are moved to the top of their scope before code execution.
No matter where the function and variables are declared, they are moved at
the top of their scope
console.log(counter); // undefined var counter = 1;
Here, undefined will be printed, no error will be shown.
console.log(counter);
let counter = 1; // Reference error - counter is not initialized.
Here, Reference error
will be thrown because the counter
variable is not initialized.
Example: Before hoisting
let result = add(x, y);
console.log(result);
function add(a, b) {
return a + b;
}
Example: After hoisting
function add(a, b) {
return a + b;
}
let x = 20,
y = 10;
let result = add(x, y);
console.log(result);
== vs ===
Example: ==
let number = 1;
let str = "1";
console.log(number == str); // true
Example: ===
let number = 1;
let str = "1";
console.log(number === str); // false
In more precise terms:
== converts both the values to the same type and then compares
=== strictly checks for equality, which is, same type and content.
this Keyword
The
this
keyword references the object of which the function is a property of. In simple words, thethis
keyword references the object that is currently calling the function
Example:
let car = {
brand: "BMW",
getBrand: function () {
return this.brand;
},
};
console.log(car.getBrand()); // BMW
Here, the output is BMW
because the method getBrand()
is called where this.brand
is being returned. Now, this
refers to the object car; and hence, this.brand
refers to car.brand
and BMW
is returned.
call(), apply() and bind()
- - `this` points to global scope.
Default Binding
- - using dot notation
Implicitly
function fun() {
console.log(this);
}
const obj = {
counter: 1,
fun: fun,
};
obj.fun();
// {counter: 1, fun: ƒ}. that is: obj
- - Force a function to use an object at their `this` place. Here, We have three options: `call()`, `apply()`, and `bind()`
Explicitly
call():
Pass in the required object as the first parameter during function call, The actual parameters are passed after the object, one after the other
function fun(param1, param2) {
console.log(this);
}
const obj = {
counter: 1,
};
const param1 = 1,
param2 = 2;
fun.call(obj, param1, param2); // {counter: 1} - the obj is binded with fun() method
apply():
Pretty much same as call()
, only difference is that parameters are passed as an
array.
function fun(param1, param2) {
console.log(this);
}
const obj = {
counter: 1,
};
const param1 = 1,
param2 = 2;
fun.apply(obj, [param1, param2]); // {counter: 1} - the obj is binded with fun() method
bind():
A new function is created where we explicitly bind the this
which is fixed. Also
known as bound functions.
function fun() {
console.log(this);
}
const obj = {
counter: 1,
};
const boundFunction = fun.bind(obj);
boundFunction(); // {counter: 1}
Local Storage and Session Storage
persist data
Local Storage
localStorage.setItem("name", "Manu"); // set an item
localStorage.getItem("name"); // retrieve the data
localStorage.removeItem("key"); // remove data
Session Storage
sessionStorage.user = {
name: "Manu",
};
Timers - setTimeout() and setInterval()
setTimeout()
The method calls a function after a specified duration of time has passed.
setTimeout(function () {
console.log("Manu");
}, 2000);
Here, "Manu" is logged after 2 seconds.
setInterval()
This method calls a function over and over again within a specific interval of time.
setInterval(function () {
console.log("Manu");
}, 2000);
Here, "Manu" is printed every 2 seconds.
clearInterval()
is used to stop the setInterval() method.
Output Based Questions on Timers:
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
console.log("3");
//Answer: "1" "3" "2"
// interviewer: what will the following code output?
const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
setTimeout(function () {
console.log("Index: " + i + ", element: " + arr[i]);
}, 3000);
}
// Output: '4 undefined' Printed 4 times.
In the above question:
- Loop is executed and initial conditions are checked.
- setTimeout() is not called -> defered to call stack(more on event loop later)
i
value is incremented and keeps on iterating.- Loop breaks,
i
= 4 and finally setTimeout() function is executed. - Since
i
= 4, Index: 4 is printed and sincearr[4]
does not exist,undefined
is printed. - This executes for 4 times since the setTimeout() was called 4 times.
Polyfills
A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it.
For Example, The interviewer might ask you to implement your own text-shadow
from scratch property or some property which is not supported by old browsers.
Most commonly asked function implementations are: map(), forEach(), filter(), reduce(), bind()
Implementing forEach() from scratch
Array.prototype.myForEach = function (callback) {
// callback here is the callback function
// which actual .forEach() function accepts
for (var i = 0; i < this.length; i++) {
callback(this[i], i, this); // currentValue, index, array
}
};
let arr = ["Moosetape", "BDFU", "Godzilla"];
arr.myForEach((item) => console.log(item));
Implementing map() from scratch
Array.prototype.myMap = function (callback) {
var arr = []; // since, we need to return an array
for (var i = 0; i < this.length; i++) {
arr.push(callback(this[i], i, this)); // pushing currentValue, index, array
}
return arr; // finally returning the array
};
let arr = ["Moosetape", "BDFU", "Godzilla"];
const result = arr.myMap((item) => console.log(item));
Implementing filter() from scratch
Array.prototype.myFilter = function (callback, context) {
arr = [];
for (var i = 0; i < this.length; i++) {
if (callback.call(context, this[i], i, this)) {
arr.push(this[i]);
}
}
return arr;
};
let arr = [
{ name: "Moosetape", songs: 20 },
{ name: "BDFU", songs: 7 },
{ name: "Godzilla", songs: 11 },
];
arr.myFilter(function (album) {
return arr.songs > 11; // providing the context here
});
Implementing reduce() from scratch
Array.prototype.myReduce = function (callback, initialValue) {
var accumulator = initialValue === undefined ? undefined : initialValue;
for (var i = 0; i < this.length; i++) {
if (accumulator !== undefined) {
accumulator = callback.call(undefined, accumulator, this[i], i, this);
} else {
accumulator = this[i];
}
}
return accumulator;
};
let arr = ["Moosetape", "BDFU", "Godzilla"];
let results = arr.myReduce(function (a, b) {
return a + " " + b;
}, "Tape - ");
console.log(results);
Implementing bind() from scratch
let name = {
first: "Sidhu",
last: "Moosewala",
};
let display = function () {
console.log(`${this.first} ${this.last}`);
};
Function.prototype.myBind = function (...args) {
// this -> display
let obj = this;
return function () {
obj.call(args[0]);
};
};
let displayMe = display.myBind(name);
displayMe(); // Sidhu Moosewala
Event Loop
This is a big one. 🥺
The event loop's job is to look at the stack and look at the task queue. If the stack is empty, it takes the first thing on the queue and pushed it on to the stack.
Now what does this even mean? Let's have a look.
Javascript is a
Single-Threaded non-blocking asynchronous concurrent language.
What makes JavaScript special is the addition things that the browser provides (Web APIs, Callback Queues and the event loop). But Natively, JavaScript just has 2 things, i.e. Heaps and Call Stack.
- Call Stack: A data Structure where function calls get stacked
- Heap: A Memory area from where the memory is allocated to processes.
Now, We need to make sure the code that we are writing is non-blocking. Non-blocking essentially means that the code is not stopping the components to wait or render for some other request.
Example:
Consider this piece of code:
function third(a, b) {
return a + b;
}
function second(n) {
return third(n, n);
}
function first() {
var element = second(7);
console.log(element);
}
first();
Iterating over the Code:
main()
function is called natively by Javascript, it gets pushed on to the call stack.main()
callsfirst()
method,first()
method gets pushed onto the call stack.first()
function calls thesecond()
function,second()
function gets onto the call stack.second()
function calls thethird()
function,third()
function gets onto the call stack.third()
function executes and returnsa + b
. After that, it gets popped off the stack.- Similarly, everything gets popped off the stack.
This stacking and removing from the stack can be blocked by some bad code that we write, or for that matter, some time consuming API calls can slow down the process (async code). To cater that, we have web APIs. Let's take an example.
console.log("Event Loop");
setTimeout(function () {
console.log("is awesome");
}, 1000);
console.log("but sometimes confusing");
/* output:
Event Loop
but sometimes confusing
is awesome.
*/
Now why is that? Let's understand.
main()
is called, gets onto the call stack.main()
callsconsole.log()
method, it gets on top of the stack.console.log()
gets executed, it gets removed from the top of the stack. We move on to the next line. -> 'Event Loop' is printed.setTimeout()
is called. Since setTimeout() is anasynchronous
piece of code, it gets removed from the stack and get into the Web APIs module. In Web APIs module, the timer forsetTimeout()
runs. When the timer forsetTimeout()
is over, theCallback
function of the setTimeout() gets into theCallback Queue
.- While this is happening, The call stack is empty and it keeps on executing the lines.
console.log()
is pushed onto the stack and executed. -> 'But Sometimes Confusing' is printed- Now since the execution is done completely and the function block is finished. Event loop checks the stack.
- If the stack is empty, event loop picks the first available function from the
Callback Queue
and pushed it into the stack. - Here,
Callback
forsetTimeout()
gets pushed onto the stack i.e.console.log()
. -> 'is awesome' is printed.
Event loop's task is to essentially check the stack, if it is empty, put the callback queue's function on the stack and repeat this entire process.
Event loop in itself is another article, For more information, watch this video full of swag
Promises
A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved (e.g., a network error occurred).
States of a promise:
- fulfilled - When the promise is
resolve()
ed. - rejected - When the promises is
reject()
ed. - pending - When the promise is still processing.
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time));
wait(3000)
.then(() => console.log("Hi Paaji!"))
.catch((err) => console.log(err)); // 'Hi Paaji!'
We can chain .then()
and .catch()
methods on promises.
Async / Await
The advantage of an async function only becomes apparent when you combine it with the await keyword. await only works inside async functions within regular JavaScript code, however it can be used on its own with JavaScript modules.
Long story short:
- Async Await can be used to handle asynchronous code in a much cleaner and smaller way.
- The return value of Async functions are always Promises that can be
await
ed and results can be generated.
// returns a promise
let hello = async () => {
return "Hello";
};
// Chain a .then() to get the value
hello().then((value) => console.log(value));
let apiCall = async () => {
const response = await fetch("someapi.cpom/json");
if (!response.ok) throw new Error("Code Failed, Boo!");
return response;
};
apiCall();
Closures
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
Before undersanding closures, lets understand
Lexical Scope
function start() {
var name = "Manu"; // name is a local variable created by start()
function displayName() {
// displayName() is the inner function, a `closure`
alert(name); // use variable declared in the parent function
}
displayName();
}
start(); // "Manu" alert box is displayed
Here:
displayName()
method is an inner function, which is only available inside the body ofstart()
method- Interesting fact is that the method
displayName()
knows that there is a variablename
OUTSIDE of thedisplayName()
's body. This is because inner functions have access to the parent's variables and methods.
Coming back to
Closures
function startClosure() {
var name = "Manu";
function displayName() {
alert(name);
}
return displayName;
}
var fun = startClosure();
fun(); // "Manu" is alerted
Precisely, A closure is the combination of a function and the lexical environment within which that function was declared.
Which means, That an inner function will know the it's surroundings, i.e. the parent scope and the collective thing
is called a closure.
Here, The instance of displayName maintains a reference to its lexical environment, within which the variable name exists. For this reason, when fun() is invoked, the variable name remains available for use, and "Manu" is passed to alert.
Prototypes
JavaScript is often described as a prototype-based language — to provide inheritance, objects can have a prototype object, which acts as a template object that it inherits methods and properties from.
Javascript implicitly / internally puts the __proto__
object. When we craete anything like a function or an object, Javascript add the proto object to it with properties and methods.
Prototypal Inheritance
In Javascript, we achieve OOPs inheritance through Prototypes.
let firstObject = {
name: "Manu",
programming: "Next.js",
age: 21,
welcome: function () {
console.log(`${this.name} write in ${this.programming}`);
},
};
let secondObject = {
name: "Leo",
age: 24,
};
secondObject.__proto__ = firstObject;
console.log(secondObject.programming);
Debouncing
Debouncing precisely is delaying some operation so that it is not called on every iteration instantly.
Explaining the above statement: Debouce is used when one wants to delay some operation with some specific amount of time.
Lets take an example:
Codesandbox:
Manu Arora's create username debounce example
import "./styles.css";
let inputEle = document.getElementById("inputElement");
let username = document.getElementById("username");
let generateUsername = (e) => {
username.innerHTML = e.target.value.split(" ").join("-");
};
inputEle.addEventListener("keyup", generateUsername, 300);
The above code takes in names, splits and joins with a hyphen, and prints the new string as a username.
The native behaviour will be to spit out on every keyup, which is browser heavy task if implemented in a large scale system.
Instead, what can be done is we can have debouncing in place. Debounce method takes in a delay, which infact, will delay the time between each keystroke (here, it'll be 300 ms) and only then the next request will be processed.
import "./styles.css";
let inputEle = document.getElementById("inputElement");
let username = document.getElementById("username");
let generateUsername = (e) => {
username.innerHTML = e.target.value.split(" ").join("-");
};
let debounce = function (cb, delay) {
let timer;
return function () {
let context = this;
clearTimeout(timer); // to clear any previous timeouts - no accumulating garbage here in my world! ;)
timer = setTimeout(() => {
cb.apply(context, arguments);
}, delay);
};
};
inputEle.addEventListener("keyup", debounce(generateUsername, 300));
We also clearInterval()
to avoid unnecessary memory leaks.
Long story short: GetUsernames will be called on keyup - but only if there is a 300ms delay between each keystroke.
Phew! That was one long article.
That's it for this blog, if you liked it please share it on Twitter or any other social media platforms.
Don't forget to look at the Resources and Snippets page where I regularly update cool stuff for developers to make their lives easier.
✌️ Thanks!
Want to hire me as a freelancer? Let's discuss.
Drop your message and let's discuss about your project.
Chat on WhatsAppDrop in your email ID and I will get back to you.