Ace the Javascript Interview

Manu Arora

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

  1. Array Methods
  2. Var, Let, Const
  3. Hoisting
  4. == vs ===
  5. this keyword
  6. call(), apply() and bind()
  7. Local Storage / Session Storage
  8. Timers - setTimeout(), setInterval()
  9. Polyfills
  10. Event Loop (The Right Way)
  11. Promises
  12. Async / Await
  13. Closures
  14. Prototypes
  15. Debouncing
  16. Babel and Webpack: Coming Soon
  17. Server Side Rendering: Coming Soon
1

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:

The following iterates over an array of numbers and returns a new array - in which every number is multiplied by 2.
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:

The following iterates over an array of numbers and filters the array in such a way that if the number is greater than 4, only then it is kept. Results are stored in a new array.
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:

Sum all the numbers of the array numbers = [1, 2, 3] and return the result

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:

Iterate over the numbers = [1, 2, 3, 4] array and check if it is even or odd.
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:

Check the age of the person in the ages array. ages = [12, 32, 42, 22, 13], if the age is greater than 18 then accept the first match, otherwise return.
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()

Method chaining can be done in map() but not in forEach()
map() returns a new Array, forEach() doesn't return a new array
both of the methods don't mutate the original array

2

Var, Let and Const

var

var is globally and functionally scoped - meaning it is accessible throughout your script or within a function
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 variables can be re-declared and re updated. It can be declared without initialization
var text = "hey bruh";
var text = "Helloooooooo!";

var text; // valid

let

let is now the preferred way of declaring and initializing variables in JavaScript.

let is block scoped. A block is a chunk of code covered by {}. Variables defined by let are only accessible within a block of code.
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 can be updated, but it cannot be re-declared within the same scope
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

Variables declared with const maintain a constant value. They share some similarities with let.

const declarations are block scoped, just like let. They can be accessed within a block only.
const declarations cannot be updated or re-declared
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 declarations must be initialized at the time of declaration
const text; // error

Note:

The interviewer might ask the difference between the three. Other popular questions revolving around var let and const cover Hoisting, which is discussed below.

3

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

whether their scope is global or local.

var is hoisted at the top of the scope and initialized with undefined
console.log(counter); // undefined var counter = 1;

Here, undefined will be printed, no error will be shown.

let is hoisted at the top of the scope and is not initialized at all.
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.

Functions hoisting - Hoisted at the top

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);

4

== vs ===

In simple terms, `==` doesn't check the type while `===` checks the type before comparing.

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

where as

=== strictly checks for equality, which is, same type and content.


5

this Keyword

The this keyword references the object of which the function is a property of. In simple words, the this 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.


6

call(), apply() and bind()

In JavaScript, we have various ways for binding `this` to a function.
  1. Default Binding

    - `this` points to global scope.
  2. Implicitly

    - using dot notation
function fun() {
  console.log(this);
}

const obj = {
  counter: 1,
  fun: fun,
};

obj.fun();
// {counter: 1, fun: ƒ}. that is: obj
  1. Explicitly

    - Force a function to use an object at their `this` place. Here, We have three options: `call()`, `apply()`, and `bind()`

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}

7

Local Storage and Session Storage

Both mechanisms are used to

persist data

in the browser level.

Local Storage

Data is not lost even after the browser is closed, or even after the OS is rebooted.
localStorage.setItem("name", "Manu"); // set an item
localStorage.getItem("name"); // retrieve the data
localStorage.removeItem("key"); // remove data

Session Storage

Data is persisted over a session. i.e. Data will be lost if the browser is closed
sessionStorage.user = {
  name: "Manu",
};

8

Timers - setTimeout() and setInterval()

The concepts are simple - Questions around these topics are tricky.

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 since arr[4] does not exist, undefined is printed.
  • This executes for 4 times since the setTimeout() was called 4 times.

9

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

10

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.

Which means it only has one call stack, one memory area.

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() calls first() method, first() method gets pushed onto the call stack.
  • first() function calls the second() function, second() function gets onto the call stack.
  • second() function calls the third() function, third() function gets onto the call stack.
  • third() function executes and returns a + 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() calls console.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 an asynchronous piece of code, it gets removed from the stack and get into the Web APIs module. In Web APIs module, the timer for setTimeout() runs. When the timer for setTimeout() is over, the Callback function of the setTimeout() gets into the Callback 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 for setTimeout() 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


11

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.

12

Async / Await

> First of all we have the async keyword, which you put in front of a function declaration to turn it into an async function. An async function is a function that knows how to expect the possibility of the await keyword being used to invoke asynchronous code.

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 awaited 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();
13

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 of start() method
  • Interesting fact is that the method displayName() knows that there is a variable name OUTSIDE of the displayName()'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.

Closures are good for data encapsulation - which is if you want to hide the implementation of some functions.
Currying is one use of closures.
Closures' variables are NOT garbage collected - memory leaks can happen.
14

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);
14

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 WhatsApp

Drop in your email ID and I will get back to you.