JavaScript  Interview  Cheat  Sheet

JavaScript Interview Cheat Sheet

HELLO, dear readers! Hope you're doing awesome and having a great week!

Welcome to my very first blog post ever!!!

Let me give a quick brief introduction of mine and then we can start.

I have been into Frontend Web Development for quite some time now, and currently aspiring to become a FullStack Developer. During this transit, I'll be sharing my learnings, observations, various strategies to tackle some bad boy weird bugs, maybe some small hacks and countless experiences that I'll be having throughout my journey.

In this blog, I'll be sharing some of the fundamentals of Javascript language and some of the most asked interview questions on this "easy-to-grab-but-not-so-easy-to-understand-fully" language of the web. So without further ado, lets get started.

Topics to be covered in this blog

  • Scope
  • Hoisting
  • Call Stack
  • Single-threaded programming language

What is a Scope?

In programming world, we declare variables and functions to contain some value or define how a piece of code should interact with another or itself to produce some predicted result. These are called identifiers which store some state of our program. And we programmers, use these identifiers to extract out the value or the logic according to our will. But here lies the question --> Can we actually extract the state according to our own free will from anywhere inside the codebase? Let's see with the first example.

let a=100; //declaring variable "a" with value 100; but where??

function randomFuncA(x){       //declaring a functionA ; but where??
    console.log("Printing the value of param x: ",x);
    let a=insideFuncB(x);           
    console.log(a);
}

function randomFuncB(){        //declaring a functionB
    return function insideFuncB(y){  //declaring another inner function; but again where??
        return y*2;
    }
}
randomFuncA(a);         //invoking or calling functionA; from where now??

/*Output: 
Printing the value of param x:  100
index.js:5 Uncaught ReferenceError: insideFuncB is not defined
    at randomFuncA (index.js:5:11)
    at index.js:14:1 */

As we saw, the first console.log did output correctly as we wanted. However when we wanted to get the value of function insideFuncB called from inside randomFuncA according to 'our will', it didn't quite work well. Straight away JS engine slammed us with Uncaught ReferenceError with the error showing as insideFuncB is not defined. But we did define insideFuncB somewhere right? So why can't we call it and get the value back?

Well, this is where the very fundamental concept of scopes come into picture.

Scope is the visibility area where the identifiers are defined and these identifiers have access to or "can see through" only this area. In other words, different identifiers can call or access other identifiers from within the same scope (area) they are defined in. JavaScript has lexical scope due to which identifiers can search in it's parent scope for missing values if not found in the current scope, but not in child scope. Therefore, variable lookup or function invocation/ access can only be possible from outwards the current scope or in the current scope, not inwards from the current scope.

Few small examples for this would be:

//Notice how func A tries to invoke func C 
//which is defined in the scope of func B, so it won't work
function A(){

    console.log(C());   //Uncaught ReferenceError: C is not defined
    function B(){
        return function C(){
            return 200;
        }
    }
}
A();

//But if we do console.log(B()()) in place of console.log(C()) 
//then it would work and happily print 200 to console
//Notice the outward(parent) access scoping as well as the current scoping
function A(){
    let xyz=200;
    function D(){
        return x=50;
    }
    return function B(){
        console.log("Hello"); //printing from current scope
        console.log(xyz);      //printing from parent scope (also it happens due to closure)
        console.log(C());      //printing from current scope, since func C() is defined in func B's scope
        console.log(D());      //printing from parent scope (again due to closure)
        function C(){
            return 12;
        }
    }
}
A()();

/* Output:
Hello
200
12
50*/

In the above piece of code, we saw a keyword called closure. Well, that requires a separate article on its own. For now, lets just agree on the fact that scope and closures run hand-in-hand. Without scopes, there will be no closures.

Now that we have understood scopes in great detail, lets analyze the very first piece of code which was giving an error.

  • randomFuncA is defined in Global scope
  • randomFuncB is defined in Global scope
  • insideFuncB is defined within the scope of randomFuncB (function scope)
  • So naturally, randomFuncA cannot directly access the nested function insideFuncB
  • To do that, randomFuncA needs to first have access to the randomFuncB's scope, i.e inside space of randomFuncB
  • Once access is verified, then only insideFuncB will be accessible to randomFuncA

Let's try to tweak the code a little maybe...

let a=100; //declaring variable "a" with value 100; in Global Scope

function randomFuncA(x){       //declaring a functionA ; in Global Scope
    console.log("Printing the value of param x: ",x);
    let a=randomFuncB()(x);           
    console.log(a);
}

function randomFuncB(){        //declaring a functionB
    return function insideFuncB(y){  //declaring another inner function; in Function Scope
        return y*2;
    }
}
randomFuncA(a);         //invoking or calling functionA; from Global Scope

/*Output: 
Printing the value of param x:  100
200 */

Hey but what is Global scope, Function scope, Lexical scope? Let's discuss that now.

Javascript has ideally 4 types of scope

  • Global Scope
  • Function Scope
  • Block Scope
  • Module Scope

While Module Scope is beyond "the scope of this blog", we'll stick to the other three scopes. But we'll definitely discuss the Module Scope in great details in future for sure.

But then what about the Lexical Scope?

Lexical Scope is more of a mechanism of scope chaining in JavaScript for lookups , while the 4 scopes are more of a scope pattern which is written by the programmer before runtime to achieve some functionality.

What is Lexical Scope?

  • As we have discussed earlier also, Javascript supports lexical scoping. Which means identifier lookup happens in the current scope where it's defined and if match not found, the lookup will happen in the scope of its immediate parent and then again the parent of it's immediate parent until it reaches the Global scope. This is also called as scope chaining.

Lexical environment is the local memory space + reference to lexical environment of parent (local memory space of its parent)

function functionA(a){
    var b=a*5;              //scope of functionA
    function functionB(c){
        console.log(a, b, c); // "a" lookup from functionA scope, "b" lookup from functionA scope
                              // "c" lookup from functionB scope
    }
    functionB(b*10);       //scope of functionA
}
functionA(5);              //Global scope

// Output: 5 25 250

What is Global Scope?

  • Global scope is where any top-level code is defined by the programmer. Identifiers defined in the 'most outward' scope (i.e Global scope) can be accessed and modified from anywhere in the program. When JavaScript engine starts running a script a Global Execution context is created. That Global Execution context contains the Global Scope, which is the top-most scope ever.
//Global Scope ---> top most scope
let firstName = "Shakya"       //Global variable 

function fullName(lastName){
    console.log(`Full Name : ${firstName} ${lastName}`) //top-level global variable 
}                                                       //can be accessed from anywhere in code

fullName("Sarkar");    //Full Name : Shakya Sarkar

What is Function Scope?

  • The function scope is the accessibility of the variables defined inside a function, these variables cannot be accessed from any other function or even outside the function in the main file.
function abc() {

    var year = 2022;

    // the "year" variable can be accessed inside this function

    console.log("The year is "+ year);
}

// the "year" variable cannot be accessed outside here

abc();              // The year is 2022
console.log(year);  // Uncaught ReferenceError: year is not defined

What is Block Scope?

The block scope can be defined as the scope of the variables inside the curly brackets { }. Now, these curly brackets can be of loops, or conditional statements, or something else. We are only allowed to refer to these variables from within the curly brackets {}. let, const are block scoped, while var is not.

function letsUnderstand(){
    {
        var a= 10;
        let b= 100;
        const c= 200; 
        console.log(a, b, c);  // 10 100 200
    }
    console.log(a);        // 10
    console.log(b, c);      // Uncaught ReferenceError: b is not defined
}
letsUnderstand();

Hoisting in Javascript

Hoisting is a JavaScript behavior commonly known for making variables and functions available for use before the variable is assigned a value or the function is defined. In effect, it puts variable, function and class declarations to the top of their scope before execution.

Let's understand this:

Execution in JavaScript is a two way process. First is the memory allocation phase and second is the code execution phase.

Therefore, the identifier declarations are first allocated memory, and this makes them available before the code is executed. This makes the identifiers "hoisted" in a way. And after memory allocation is completed, then they are assigned actual values and excecuted line by line.

Hoisting variables

var declarations hoisting

console.log(a);  //undefined
var a = 100;   
console.log(a);  //100

First, the declarations are allocated memory, i.e var a is allocated memory and assigned "undefined". Then the console logs are executed line by line. Now here, since we can, in a way, access the variable a before the declaration in the first console.log, we could say that var declarations are hoisted.

The piece of code works as if:

//var a;     //var a is hoisted; undefined since no initialization is done
console(a);  // hence, undefined
a=100;   //now initializing value 100 to a
console.log(a); //100

let and const declarations hoisting

console.log(name); // Uncaught ReferenceError: Cannot access 'name' before initialization
let name = "Shakya";
console.log(name);
console.log(surname);  //Uncaught ReferenceError: Cannot access 'surname' before initialization
const surname="Sarkar";
console.log(surname);

Hmm...in the above two pieces of code, each throws a ReferenceError in the first line and ends execution. Why is that so? So does that mean let and const declarations are not hoisted? Well, all variable declarations are hoisted. let and const are also assigned undefined values. The reason why undefined is not printed and ReferenceError is thrown is due to the fact that let and const are hoisted and declared in a different Global scope (Script scope) than the regular actual Global scope (window).

let_hoisting.png Therefore, if they are used before initialized, we will get a ReferenceError.

Function hoisting

callFunction();

function callFunction(){
    console.log("It works perfectly fine");
}

As with variables, JavaScript puts the whole function into memory before executing the code in that scope. Therefore, hoisting allows us to call the callFunction() function before it is defined later in the code.

Although function declarations work like this, function expressions or arrow functions dont work since variables are used to reference the function body. Therefore, the rules of variable hoisting applies in here.

Class hoisting

Class declarations are also hoisted in JavaScript. A class declaration is uninitialized when hoisted. In other words, while JavaScript can find the reference for a class we create, it cannot use the class before it is defined in the code.

Let's look at the below example for clarity:

let obj = new Car();
obj.name = "Lamborghini";
obj.price = 1000000;

console.log(obj); // Uncaught ReferenceError: Cannot access 'Person' before initialization"

class Car {
    constructor(name, price) {
      this.name = name; 
      this.price = price;
    }
}

The ReferenceError is similar to what happens when we try to access a variable declared with let or const before it is initialized in our script.

What is a Call Stack?

Before we answer that, we all are aware of the Stack data structure which works in LIFO (last-in-first-out) fashion. Elements are stacked on top of one another, thereby the very last element is removed last.

JavaScript Engine uses this very data structure to record where in our program we are. We can think of the elements as functions, so whenever a function is encountered in our code it pushes into the stack and when the function is returned or reaches the end, it pops out of the stack. The Call Stack is the heart of the Javascript Engine, without it no JS code would have executed.

function main(){
    function firstFunc(){
        function innerFirstFunc(){
            function innerMostFunc(){
                console.log("Hello there!");
            }
            innerMostFunc();
        }
        innerFirstFunc();
    }
    function secondFunc(){
        console.log("Okay cool!")
    }
    firstFunc();
    secondFunc();
}
main();

Notice the Call Stack consisting of the functions in order they have been invoked

The anonymous function is the Global Exceution Context. Whenever a JS code runs for the first time, the global execution context is created. Eventually all the functions that we are seeing, they are the execution contexts of each of the functions.

Call_stack.png

After the complete execution of firstFunc() it gets popped out of stack and secondFunc() gets pushed into the stack call_stack_2.png

Finally, after complete execution of secondFunc() it gets popped out of stack. Only main() and Global Execution context remains in the stack Call_stack_3.png

Execution Context itself is a vast topic in itself, so I'll cover that up in its separate article, otherwise you'll cuss me for this already long blog post.

For now, lets agree on the fact that each function invocation creates its own execution context. Global Execution Context always gets pushed first into the Call Stack whenever a program runs and subsequently other execution contexts on respective function calls.

Single-threaded language

A thread is a process containing specific set of instructions needed to perform a certain task.

Javascript is a single-threaded programming language. Which means it has only one call stack, one heap, only one thread and can only do one thing at a time. It is therefore synchronous in nature. It solves the problem of deadlock, increased complexity, concurrency problems often faced in mult-threaded languages. But while performing a heavy I-O intensive task, it'll block the entire thread and won't be able to do anything else until that task is finished. So is it really advantageous?

So that means, if we click the search button in Google for fetching some data from some remote server while simultaneously binge watching Netflix, it won't be possible? Well, according to the grammar of JavaScript, we can't do that. But in reality, we CAN!!

Enter Browser APIs, Event Queue, Micro-tasks Queue and the main hero Event Loop!

These are not part of the JavaScript Engine, but what the Browser offers us directly out of the box. When we want to achieve asynchrony, — by making a fetch request for example — the task is handed over to the browser, which handles it in the background. When the task is finished, it is placed into the tasked queue, which eventually returns it to the call stack and executes happily.

Combining these superpowers, JavaScript becomes truly asynchronous, non-blocking programming language. Again, a completely separate article will be published regarding this concurrency model of JavaScript.

That's all for now!! Hope you liked my first blog post and learned something new from this. If you liked this, do give it a nice reaction as it'll hugely motivate me. Let me know in the comment section for any updates, suggestions or your honest thoughts on this.

Thank you readers! Happy Learning! Happy Coding!!