JavaScript Interview Preparation Cheatsheet

JavaScript Interview Preparation Cheatsheet

In this article, we'll cover a few mostly asked interview questions that will help you to crack your next JS interview.

Scope

The scope is an important concept that determines the accessibility of variables, functions, and objects. In JavaScript, there are two types of scopes

  • Local Scope
  • Global Scope

1. Local Scope

Variables that can be used in a specific part of the code are considered to be in a local scope. And these variables are called Local Variables. There are two types of local scope in JavaScript,

  • Block Scope
  • Function Scope

1.1 Block Scope

Block scope is an area within conditions (if or switch) or loops (for and while) or whenever you see {} it is a block. In a block, you can declare variables using const and let keywords. These variables exist only within the corresponding block.

if (false) {
   const msg = 'Hello World';
   console.log(msg); // 'Hello World'
}
console.log(msg); // ReferenceError

The first console.log(msg) works because the variable is declared inside the if block, therefore the msg is accessed from the scope where it is defined.

The second console.log(msg) throws a reference error because the variable is accessed outside of its scope.

The code blocks of if, for, while, and standalone also delimit a scope.

Keyword var is not block scoped

1.2 Function Scope

When you declare a variable in a function, that variable is only visible within the function.

function call() {
   var msg = 'Make a call';
   console.log(msg);
}
call(); // 'Make a call'
console.log(msg); // Error: msg is not defined

call() function body creates a scope. The variable msg is only accessible within the function scope and if we try to access it outside of its scope then it'll throw an error.

2. Global Scope

The global scope is the outermost scope and the variables can be accessed from any inner(Local) scope.

var msg = 'Hello World';
function readMsg() {
   console.log(msg);
}
readMsg(); // 'Hello World'

Variables declared inside the global scope are named Global Variables, these variables can be accessed from any scope.

Lexical Scope

When a function is defined inside another function, the inner function can access the variables of the outer function. This operation is called Lexical scoping.

function outerFunc() {
   var msg = 'Hello World';

   function innerFunc() {
      console.log(msg);
   }

   innerFunc();
}
outerFunc(); // 'Hello World'

The inner function is lexically bound to the execution context of its outer function.

Single Thread

Javascript is a synchronous language by default. This means that all the statements and functions execute one after the other in a predefined order. Javascript behaves this way because it has only a single thread of execution.

Thread in computer science is the execution of running multiple tasks or programs at the same time. Each unit capable of executing code is called a thread.

Single-Threaded

JavaScript is known to be single-threaded because of its property of having only one call stack, which some other programming languages have multiple. JavaScript functions are executed on the call stack, by LIFO (Last In First Out). For example, we have a piece of code like this:

const foo = () => {
  const bar = () => {
    console.trace();
  }
  bar();
}
foo();

And the call stack will have foo to enter into the call stack, then bar.

single-threaded-1.png

After bar() is done, it will be popped off from the call stack, followed by foo(). You will see an anonymous function underneath when printing out the stack trace, which is the main thread's global execution context.

single-threaded-2.png

This seems to be logical as JavaScript is a single-threaded language and there is only a single flow to execute all these functions. However, in the case that we are having some unpredictable or heavy tasks in the flow (for example making an API call), we do not want them to block the execution of the remaining codes (or else users might be staring at a frozen screen). This is where the asynchronous JavaScript comes in.

Call Stack

A call stack is a mechanism for an interpreter (like the JavaScript interpreter in a web browser) to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc.

  • When a script calls a function, the interpreter adds it to the call stack and then starts carrying out the function.
  • Any functions that are called by that function are added to the call stack further up and run where their calls are reached.
  • When the current function is finished, the interpreter takes it off the stack and resumes execution where it left off in the last code listing.
  • If the stack takes up more space than it had assigned to it, it results in a "stack overflow" error.

    Example

function greeting() {
   // [1] Some code here
   sayHi();
   // [2] Some code here
}
function sayHi() {
   return "Hi!";
}

// Invoke the `greeting` function
greeting();

// [3] Some code here

The code above would be executed like this:

  1. Ignore all functions, until it reaches the greeting() function invocation.
  2. Add the greeting() function to the call stack list.

    Note: Call stack list: - greeting

  3. Execute all lines of code inside the greeting() function.

  4. Get to the sayHi() function invocation.
  5. Add the sayHi() function to the call stack list.

    Note: Call stack list: - sayHi - greeting

  6. Execute all lines of code inside the sayHi() function, until reaches its end.

  7. Return execution to the line that invoked sayHi() and continue executing the rest of the greeting() function.
  8. Delete the sayHi() function from our call stack list.

    Note: Call stack list: - greeting

  9. When everything inside the greeting() function has been executed, return to its invoking line to continue executing the rest of the JS code.

  10. Delete the greeting() function from the call stack list.

    Note: Call stack list: EMPTY

Hoisting

Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution by the parser which reads the source code into an intermediate representation before the actual execution starts by the JavaScript interpreter. So, it doesn’t matter where variables or functions are declared, they will be moved to the top of their scope regardless of whether their scope is global or local.

This means that

console.log (hi);     
var hi = "say hi";

Actually is interpreted to this

var hi = undefined;
console.log (hi);
hi = "say hi";

So, as we saw just now, var variables are being hoisted to the top of their scope and are being initialized with the value of undefined which means that we can actually assign their value before actually declaring them in the code like so:

hi = “say hi”
console.log (hi); // say hi
var hi;

Now, what about functions?

Well, if we are talking about function declarations, we can invoke them before actually declaring them like so:

sayHi(); // Hi

function sayHi() {
  console.log('Hi');
};

Function expressions, on the other hand, are not hoisted, so we’ll get the following error:

sayHi(); //Output: "TypeError: sayHi is not a function

var sayHi = function() {
  console.log('Hi');
};

ES6 introduced JavaScript developers to the let and const keywords. While let and const are block-scoped and not function scoped as var it shouldn’t make a difference while discussing their hoisting behavior. We’ll start from the end, JavaScript hoists let and const.

Let’s first explore the let keyword behavior.

console.log(hi); // Output: Cannot access 'hi' before initialization 
let hi = 'Hi';

As we can see above, let doesn’t allow us to use undeclared variables, hence the interpreter explicitly outputs a Reference an error indicating that the hi variable cannot be accessed before initialization.

The same error will occur if we change the above let to const

console.log(hi); // Output: Cannot access 'hi' before initialization
const hi = 'Hi';

So, to sum up, the JavaScript parser searches for variable declarations and functions and hoists them to the top of their scope before code execution, and assigns values to them in the memory so in case the interpreter will encounter them while executing the code he will recognize them and will be able to execute the code with their assigned values. Variables declared with let or const remain uninitialized at the beginning of execution while that variable declared with var are being initialized with a value of undefined.

Conclusion

I hoped you enjoy this article and it made some sense in how scope, call stack, and hoisting work with detailed explanations. If you liked it, you are more than welcome to make some comments below or leave a like (: