JavaScript Interview Cheat-sheet

JavaScript Interview Cheat-sheet

Scope & Scope Chaining | Call-Stack | JavaScript is Single Threaded | Hoisting

ยท

9 min read

Introduction

Hey there, I hope this article finds you in good health. In this article, we are going to understand some important as well as somewhat tricky topics in JavaScript and these topics will surely help you with the javascript-based job interview questions.

We are going to get the answers to the below questions in this article:

Q.1 What is scope, lexical scope and scope chaining?

Q.2 What is call-stack?

Q.3 Why JavaScript is single-threaded?

Q.4 What is hoisting?


1. Scope

Scope of a variable is the context within which that variable is visible or available for use. In simple words, scope of a variable is that part/area in your code within which you can access that variable without getting any reference error- ReferenceError: <variable-name> is not defined

Based on where and in what context a variable is accessible, the scope can be of below types:

1.1 Global Scope

A variable with a global scope will be accessible within the entire code of a script being executed.

var x = 2;  // x has a global scope
function printX(){
  console.log(x);  // x is not found in current scope i.e. inside printX() function but found in outer i.e. global scope
}

printX();  // OUTPUT: 2

1.2 Function Scope

A variable with a function-level scope will be accessible only within the function where it was declared.

function printX(){
  let x = 2;  // x has a function-level scope within this printX function
  console.log(x);  // x is found in current scope i.e. function-level scope
}
printX(); // OUTPUT: 2
console.log(x); // throws ReferenceError: x is not defined

1.3 Block Scope

A variable with a block-level scope will be accessible only within the code block where it was declared.

function printY(){
  let x = 4;
  if(x%2==0){
    let y = 2;  // y is accessible inside this `if` code-block
    console.log(y); // OUTPUT: 2
  }
  console.log(y); // throws ReferenceError: y is not defined
}
printY();

Note: Notice how in function printY(), y is accessible only inside the if block where it was defined and not inside the whole function inside which it exists.

Lexical Scope

Lexical Scope is not actually a type of scope but rather it explains how the functions or variables behave based on where they are defined and not where they are invoked, called or accessed.

let y = 2; // notice this variable "y" has global scope
function printY(){ // Notice this function printY() has global scope
    console.log(y); // OUTPUT: 2
}
function accessY(){
    let y = 4; // notice this "y" has function-level scope but it is not accessible to printY() function which is invoked below
    printY(); // notice that printY() is invoked here in this function accessY()'s scope but doesn't have access to variable "y" defined above.
}
accessY(); // notice that accessY() is invoked here in global scope

Note: Notice how in function accessY(), although printY() is invoked there but it doesn't have access to variable y = 4 defined above it. Due to this reason, when printY() function tries to access y inside its definition, it accesses the y=2 which was defined in global scope. If there was no variable y present in global scope, then we would receive an error- ReferenceError: y is not defined in those cases.

So, with above example we can understand how for a variable or a function, the lexical scope exists where the function or variable is defined and not where it will be invoked.

Scope Chaining

Scope chaining is the concept of how Javascript engine/interpreter navigates through the code to accesses the variables/functions defined in different scopes thereby making a a chain of scopes between the global execution-context and inner function-level execution contexts.

//Global Scope
let a = "A";

function printAB(){ // An Outer function existing in global scope
  //Function scope
  let b = "B"; 

  printA(); // being invoked here in function scope of printAB() function

  if(b != null || b != ""){ // an inner block existing in function scope of printAB() function
    console.log(b);
  }

  function printA(){ // an inner function existing in function scope of printAB() function
    console.log(a);
  }
}

printAB(); // invoked in global scope

// OUTPUT: 
// A
// B

Now, we will understand how there is a scope chain being created by understanding what is happening in above code.

  • First, we have a global scope where we have a globally-scoped variable a and a function printAB() which will be available throughout the execution of this script of code.
  • second, when we call/invoke printAB() function in the global scope, a separate function-level execution context is created for printAB() function and this creates the first scope-chain between the global execution-context and the function-level execution context of printAB() function. blog-img-01.png
  • Inside this function-level execution context of printAB() function, we have a variable b, an if-block code and a function printA() defined in it.
  • Now, for executing this if-block code and printA() function, separate execution-contexts will be created for each of them, which will further extend the scope-chain. So, if you notice in the above image, the arrows between the various execution contexts represent the scope-chain between the execution contexts.

2. Call-Stack

  • A call-stack is a stack like mechanism used by the javascript engine/interpreter to keep track of its place in a script which has one or more functions defined in it for execution.

  • A call-stack as the name suggest follows LIFO philosophy just like the stack data-structure i.e. what comes first in it, will come out at the end.

  • A Javascript engine uses a call-stack to manage various execution contexts i.e. global and function-level execution contexts.

  • In a call-stack, the global variables/functions enters first, then when the outer functions are called and executed one after another, the stack keeps getting piled up over and above. And inside each of these outer functions, its inner-functions and code are then executed one after another. And then one by one, starting from the inner-most functions and variables to the outer most functions, they will keep getting popped out or being removed from the stack one by one until only global level functions and variables remain, and once nothing is left to be executed, call-stack will become empty.


3. JavaScript is Single-Threaded!

JavaScript is single-threaded? yes, it is.

What does it mean? It means that in javascript, code is executed line by line and no simultaneous or parallel execution of different lines of code is not possible at the same moment of time.

But then how does JavaScript manages to load the images and other lines of code, which require time to be executed, without stopping the flow of execution of other code? Well, the browser and Javascript engine helps here, they provide us with Event Loops, Callback queue and WebAPI. So, for any such code(like loading images from a server) that might stop the normal flow of execution, that type of code is handled by these Event Loops, Callback queue and WebAPI.

blog-img-02.png

For example, let's say within our script we have a timeout function which will stop the normal flow of execution for a specified time-period and will resume the flow after the specified time has passed, to avoid this stoppage of flow of execution of further code in the script, in that case, this timeout function when invoked will go to Call-Stack and then get moved from there to Callback queue because it doesn't need to be executed right now, and this will prevent the stoppage of normal flow of execution and so the further code in the script will be executed. But in the meanwhile, the Event Loop will keep checking the Call-stack until nothing is left to be executed, and once it is empty, Event Loop will move the timeout function from Callback queue to Call-Stack for its execution after the specified time has passed.


4. Hoisting

Hoisting is a concept which defines one of the key behaviors of how JavaScript works.

Basically, it is related with how the javascript interpreter first scans the variables, functions, classes declared in your script before starting the execution of code and the functions invoked in the script.

Below are the various types of hoisting based on what kind of entity it deals with:

4.1 Variable Hoisting

When it comes to hoisting of variables, Javascript only scans their declarations before the start of execution of the code in script.

This means if we will try to access a variable before the line of code where it was declared and Initialized in the script, then we won't be able to access its value.

console.log(a); // OUTPUT: undefined
console.log(b); // OUTPUT: ReferenceError: b is not defined
var a = 4;
let b = 5;

However, there's a slight difference of behavior when it comes to declaring variables using var keyword versus let and const keywords.

For var declarations, although we won't be able to access their assigned values, but javascript by default assigns an undefined value to such variables.

For let and const declarations, javascript doesn't assigns any default value and hence, we get ReferenceError when we try to access such variables before the line of code where they are declared and initialized.

4.2 Function Hoisting

When it comes to function hoisting, javascript scans both the declaration as well as their function definition.

This means we can access and call a function even before the line of code where it was defined.

printX(5); // OUTPUT: 5
printY(4); // OUTPUT: ReferenceError: printY is not defined

function printX(x){
  console.log(x);
}

let printY = function(y){
  console.log(y);
}

However, there's a catch here, as you would notice in above code, we got ReferenceError when we tried to access printY() function. This is because, as we learned earlier, javascript doesn't scans variable's value, and so since we have declared printY using let keyword and then assigned a function to it, so javascript before starting the execution of code in the script, treated the printY as a variable and not a function. Hence, when we tried to access it, we got ReferenceError.

4.3 Class Hoisting

Although Classes in javascript are special functions, but when it comes to hoisting they behave same as the variables do and not like functions.

This means if we will try to access a class before the line of code where it was declared and Initialized in the script, then we won't be able to access it and will receive a ReferenceError.

let Person01 = new Person(); // OUTPUT: ReferenceError: Cannot access 'Person' before initialization
class Person{
  constructor(name, age) {
    this.name= name;
    this.age= age;
  }
}

Thank you for the read and your time. I hope this article has helped you get equipped with more confidence than before when it comes to Javascript based interview questions. Please do share your honest feedback in the comments. If possible, I will surely try to make improvements next time, based on your helpful feedback.

Thanks and until next time,

Happy Learning! ๐Ÿ˜Š

ย