JavaScript Interview Notes: Core
Mechanics
1. The Execution Context
Golden Rule: "Everything in JavaScript happens inside an Execution Context."
You can think of the Execution Context as a big box or container where JavaScript code is
evaluated and executed. It has two main components:
Component Official Name How It Works
Memory Component Variable Environment Stores all variables and
functions as key-value
pairs. (e.g., a: 10, fn: { ... }).
Code Component Thread of Execution The place where code is
executed one line at a time.
JavaScript Nature: JavaScript is a synchronous, single-threaded language. This means it
can only execute one command at a time, in a specific order.
2. The Two Phases of Execution
When a JavaScript program is run, a Global Execution Context (GEC) is created. Execution
always happens in two distinct phases:
Phase 1: Memory Creation Phase
● The engine skims through the entire code line by line and allocates memory space for all
variables and functions.
● Variables: Initially assigned the special value undefined.
● Functions: The entire code block of the function is stored in memory.
Phase 2: Code Execution Phase
● The engine goes through the code again, executing it line by line.
● Variables are assigned their actual values.
● When a function is invoked (called), a brand new, mini Function Execution Context is
created.
3. Tracing Code Execution (Interview Example)
var n = 2;
function square(num) {
var ans = num * num;
return ans;
}
var square2 = square(n);
var square4 = square(square2);
Step-by-Step Trace:
1. Global Memory Phase: n is undefined, square holds the function code, square2 is
undefined, square4 is undefined.
2. Global Execution - Line 1: n is assigned the value 2.
3. Global Execution - Line 6: square(n) is called. A new Function Execution Context is
created.
○ Local Memory Phase: num is undefined, ans is undefined.
○ Local Execution Phase: num gets 2. ans gets 4.
○ The return keyword tells the function to return control (and the value 4) back to the
Global Execution Context.
○ The local Execution Context is completely destroyed.
4. Global Execution Continues: square2 gets the returned value 4.
4. The Call Stack
To manage the creation and deletion of all these nested Execution Contexts, the JavaScript
engine uses a Call Stack.
● Mechanism: LIFO (Last In, First Out).
● Bottom of Stack: The Global Execution Context is always at the bottom.
● Push: Whenever a function is invoked, its new Execution Context is pushed onto the
stack.
● Pop: Whenever a function hits return (or finishes), its Execution Context is popped off the
stack, and control goes back to the context immediately below it.
Also known as: Execution Context Stack, Program Stack, Control Stack, Runtime Stack,
Machine Stack.
5. Hoisting in JavaScript
Definition: Hoisting is a phenomenon in JavaScript where you can access variables and
functions even before you have initialized them or declared them in the code, without getting
an error.
Why does it happen?
Because of Phase 1: The Memory Creation Phase. Before the engine executes a single line of
code, it has already scanned the script and allocated memory for variables and functions.
Code Examples (Tracing Hoisting)
Scenario 1: The "Upside Down" Test
getName(); // Prints: "Namaste Javascript"
[Link](x); // Prints: undefined
var x = 7;
function getName() {
[Link]("Namaste Javascript");
}
● Why it works: Even though we call them before they are written, the Memory Phase
has already stored the entire getName function and allocated memory for x (giving it
the default placeholder undefined).
Scenario 2: The Arrow Function Trap
getName(); // Throws Error: TypeError: getName is not a function
var getName = () => {
[Link]("Namaste Javascript");
}
● Why it breaks: Because getName is declared with var, it is treated as a variable, not a
function. In the Memory Phase, getName is assigned undefined. When execution
reaches line 1, it tries to invoke undefined(), which causes a TypeError.
Scenario 3: The "Not Defined" Error
getName(); // Prints: "Namaste Javascript"
[Link](x); // Throws Error: ReferenceError: x is not defined
function getName() {
[Link]("Namaste Javascript");
● Why it breaks: We completely removed the declaration for var x. Because it doesn't
exist anywhere in the code, the Memory Phase never allocated space for it. The engine
throws a ReferenceError because x is literally "not defined", which is distinctly
different from being initialized as undefined.
How different types of code are Hoisted:
● Standard Functions:
○ Behavior: Fully hoisted.
○ Result: The exact function body is copied into memory. You can safely call the function
lines before it is actually written in your code.
● Variables (var):
○ Behavior: Partially hoisted.
○ Result: Memory is allocated, but the variable is initialized with the special placeholder
value: undefined. If you print a var before assigning it, you get undefined.
● Arrow Functions & Function Expressions:
○ Example: var getName = () => { ... }
○ Behavior: Treated exactly like variables, NOT like functions.
○ Result: Memory is allocated for getName and it is initialized with undefined. If you try to
invoke getName() before the code execution reaches its definition, you will get an error:
TypeError: getName is not a function (because you are trying to invoke undefined).
Interview Trap: "Undefined" vs. "Not Defined"
● undefined: The variable exists in memory (it was hoisted during Phase 1), but the code
execution hasn't reached the line where it gets assigned an actual value yet.
● Not defined: The variable does not exist in memory at all (you never wrote it in your
code). If you try to access it, JavaScript throws a ReferenceError: x is not defined.
6. How Functions Work & Variable Environments
Core Concept: Every time you invoke a function, JavaScript creates a brand new Execution
Context with its own independent Memory Component (Variable Environment).
Tracing Multiple Contexts (Interview Example)
var x = 1;
a();
b();
[Link](x);
function a() {
var x = 10;
[Link](x);
}
function b() {
var x = 100;
[Link](x);
}
Output: 10, 100, 1
Why does this happen? Even though the variable is named x in all three places, they do not
overwrite each other. They live in completely different memory spaces (Execution Contexts).
1. Global Execution Context: x is initialized to 1.
2. Function a() is called:
○ A new Execution Context is pushed to the Call Stack.
○ In a's local memory, x is 10.
○ It prints 10.
○ The context is destroyed, and popped off the stack.
3. Function b() is called:
○ A new Execution Context is pushed to the Call Stack.
○ In b's local memory, x is 100.
○ It prints 100.
○ The context is destroyed, and popped off the stack.
4. Back to Global:
○ [Link](x) runs. The engine looks in the current (Global) Memory
Component.
○ It finds the global x which is still 1, and prints 1.
Browser DevTools Connection
If you place a debugger; keyword in your code and open the Chrome Developer Tools, you
can physically see this happening:
● Call Stack Panel: You will see anonymous (the Global Context) at the bottom, and
functions stacking on top of it as they are called.
● Scope Panel: You will see the Local Scope (the current function's variable environment)
and the Global Scope. The engine always checks the Local Scope first!
7. The Shortest JavaScript Program (window & this)
The Shortest Program: An absolutely empty file is the shortest JavaScript program.
What happens under the hood? Even if you don't write a single line of code, the JavaScript
engine still does a lot of work behind the scenes. It creates the Global Execution Context
(GEC) and allocates global memory space.
Along with the GEC, the JS engine also creates two important things automatically:
1. The Global Object: In the case of a browser, this object is called window. (In other
environments like [Link], it has a different name, like global). It contains a massive
collection of built-in functions and variables provided by the browser.
2. The this keyword: At the global level, this points directly to the global object.
Proof in the browser console:
this === window; // Returns: true
The Global Space
Whenever you create variables or functions outside of any specific function, they are attached
to the Global Space. Because they are in the global space, they get attached to the window
object.
Code Example:
var a = 10;
function b() {
var x = 10;
}
[Link](window.a); // Prints: 10
[Link](a); // Prints: 10
[Link](this.a); // Prints: 10
● Why does this work? Because a is declared in the global space, the engine attaches it
to the window object. Accessing a, window.a, or this.a from the global level all point
to the exact same place in memory.
● What about x? x is not in the global space. It is local to function b. If you try to do
[Link](window.x), it will return undefined because it was never attached to
the global object.
8. Scope, Lexical Environment, & The Scope Chain
Definitions
● Scope: Simply put, scope means where you can access a specific variable or function in
your code. (e.g., "Is variable b inside the scope of function a?")
● Lexical: The word "lexical" means hierarchy, sequence, or physical placement. Where a
function is physically written inside your code determines its lexical scope.
● Lexical Environment: Whenever an Execution Context is created, a Lexical Environment
is also created.
○ Lexical Environment = Local Memory + Reference to the
Lexical Parent's Lexical Environment
The Scope Chain (How the Engine Finds Variables)
When the JavaScript engine needs to find the value of a variable, it follows a specific path
called the Scope Chain:
1. It looks in the Local Memory of the current Execution Context.
2. If the variable is not there, it uses the Lexical Parent Reference to go to the parent's
Execution Context and checks its memory.
3. It keeps traveling up this chain of parent references until it finds the variable.
4. If it reaches the Global Execution Context and still can't find it, the parent reference of
the Global Context points to null. The engine stops searching and throws a
ReferenceError: [variable] is not defined.
Interview Code Examples (Tracing the Scope Chain)
Example 1: Basic Lexical Scope
function a() {
[Link](b); // Engine looks for 'b' inside a()'s local memory. Not found.
// Follows reference to parent (Global Scope). Finds 'b' = 10.
}
var b = 10;
a();
// Output: 10
Example 2: Deep Nesting (The Scope Chain in Action)
function a() {
function c() {
[Link](b);
}
c();
}
var b = 10;
a();
// Output: 10
Behind the Scenes Trace for Example 2:
1. Engine is at [Link](b) inside c(). It checks c's local memory. b is not there.
2. Engine follows reference to c's lexical parent (a()). It checks a's local memory. b is not
there.
3. Engine follows reference to a's lexical parent (Global Context). It checks Global
memory. Finds b = 10. Prints 10.
Example 3: The "Not Defined" Error (Scope is One-Way)
function a() {
var b = 10; // 'b' is locally scoped to function a()
}
[Link](b);
a();
// Output: ReferenceError: b is not defined
● Why it breaks: The Global Execution Context is trying to print b. It checks its own
memory and doesn't find it. The parent of Global is null, so it stops searching and
throws an error.
● Important Rule: A child function can reach out into its parent's memory, but a parent
cannot reach into a child's local memory. The Scope Chain only goes UP, never DOWN.
9. let & const in JS and the Temporal Dead Zone (TDZ)
Are let and const hoisted?
Yes! let and const declarations are hoisted. However, they are hoisted very differently than
var:
1. They are allocated memory, but they are not initialized with undefined.
2. They are stored in a separate memory space (often called a "Script" or "Block" scope in
dev tools), not in the Global object (window).
The Temporal Dead Zone (TDZ)
Definition: The Temporal Dead Zone is the time period between when a let or const variable
is hoisted (memory allocated) and when it is actually initialized with a value in your code.
● If you try to access a variable while it is in the TDZ, JavaScript will throw a
ReferenceError.
Code Example: The TDZ in Action
[Link](a); // ReferenceError: Cannot access 'a' before initialization
// ^--- TDZ for 'a' starts here
let a = 10; // <--- TDZ for 'a' ends here
[Link](a); // 10
[Link](window.a); // undefined (let is NOT attached to the window object)
Understanding JavaScript Errors (Interview Goldmine)
Interviewers love to test if you know the difference between these three errors:
1. ReferenceError: Thrown when you try to access a variable that is not in the memory
space at all (Not Defined) OR when you try to access a let/const variable that is still
inside its Temporal Dead Zone.
2. SyntaxError: Thrown when you break the grammatical rules of JavaScript. The engine
spots this before the code even starts running.
// SyntaxError: Identifier 'x' has already been declared
let x = 10;
let x = 20; // You cannot re-declare let/const in the same scope.
// SyntaxError: Missing initializer in const declaration
const y; // You must assign a value to const on the exact same line.
3. TypeError: Thrown when a variable exists, but you are trying to do an operation that
doesn't match its data type (e.g., trying to change a constant value).
const z = 100;
z = 200; // TypeError: Assignment to constant variable.
Quick Summary: var vs let vs const
Feature var let const
Hoisted? Yes (initialized as Yes (but in TDZ) Yes (but in TDZ)
undefined)
Attached to Yes No No
window?
Can be Yes No No
re-declared?
Can be Yes Yes No
re-assigned?
● Best Practice Tip: To avoid the Temporal Dead Zone entirely, always declare and
initialize your variables at the very top of your scope!
10. Block Scope & Shadowing in JS
What is a Block?
A block is defined by curly braces { }. It is also known as a Compound Statement.
● Purpose: It is used to combine multiple JavaScript statements into one group. We use
blocks when JavaScript expects exactly one statement, but we want to execute multiple
statements (like inside an if statement or a for loop).
if (true) {
// This is a block!
var a = 10;
[Link](a);
}
What is Block Scope?
Block Scope dictates that all variables and functions declared inside the block can only be
accessed inside that block.
● let and const are Block-Scoped: If you declare them inside a { }, they get their own
separate memory space reserved strictly for that block. They cannot be accessed from
the outside.
● var is NOT Block-Scoped: var variables ignore the block and get attached to the
Global memory space.
Code Example:
{
var a = 10;
let b = 20;
const c = 30;
}
[Link](a); // 10 (var leaked out to Global space)
[Link](b); // ReferenceError: b is not defined (let is trapped in the block)
Shadowing in JavaScript
Shadowing occurs when you have a variable declared outside a block, and you declare a new
variable with the exact same name inside the block. The inner variable "shadows" the outer one.
Scenario 1: Shadowing with var (Modifies Global Space)
var a = 100;
{
var a = 10; // Shadows the outer 'a'
[Link](a); // Prints: 10
}
[Link](a); // Prints: 10
● Behind the scenes: Because var is not block-scoped, both a declarations point to the
exact same memory location in the Global object. The inner var a = 10 overwrites
the original value!
Scenario 2: Shadowing with let (Independent Memory)
let b = 100;
{
let b = 20; // Shadows the outer 'b'
[Link](b); // Prints: 20
}
[Link](b); // Prints: 100
● Behind the scenes: This behaves differently! The outer b = 100 is stored in the "Script"
scope. The inner b = 20 creates a completely separate space in "Block" scope
memory. They do not overwrite each other.
Illegal Shadowing
There are rules to shadowing. You cannot shadow a let variable with a var variable inside a
block.
let a = 20;
{
var a = 20; // SyntaxError: Identifier 'a' has already been declared
}
● Why it is illegal: var tries to attach itself to the Global scope, but let a = 20 is
already in the broader scope boundary preventing a re-declaration in that same
boundary.
● Note: You can legally shadow a var with a let because the let will just safely contain
itself inside the block memory.
Lexical Block Scope
Just like functions, blocks follow the rules of Lexical Scope. If a block is nested inside another
block (or a function), the inner block can reach out and access variables in its parent block by
following the Scope Chain.
Code Example:
let a = 20;
{
let b = 30;
{
let c = 40;
[Link](a); // Prints 20 (Found in parent's lexical scope)
[Link](b); // Prints 30 (Found in parent's block scope)
[Link](c); // Prints 40 (Found in local block scope)
}
}
● Behind the scenes: When [Link](a) runs inside the deepest nested block, the
JS engine first checks the local block memory. It doesn't find a, so it uses the Lexical
Parent Reference to check the outer block. It doesn't find it there either, so it moves up
again to the parent lexical scope (the Global/Script scope), where it finally finds let a
= 20;.
11. Closures in JavaScript
Definition of a Closure
A Closure is a function bundled together with its lexical environment. In other words, a closure
gives you access to an outer function's scope from an inner function. Even when a function is
executed outside its original scope, it maintains access to the variables from its lexical parent
scope.
Key Concepts Under the Hood
1. Lexical Scoping: Functions remember the environment in which they were created. The
inner function always has access to the variables of its outer (parent) function because of the
Lexical Scope chain.
2. Function Returns: When a function returns another function, the outer function's execution
context is destroyed and completely removed from the Call Stack. However, the returned inner
function still "remembers" and holds onto the variables from the outer scope via a closure.
3. Reference vs. Value (Crucial Interview Trap): Closures do not just copy the value of a
variable; they store a reference to the variable's memory location.
Interview Code Examples
Example 1: Basic Closure (Returning a Function)
function outer() {
var a = 10;
function inner() {
[Link](a);
}
return inner;
}
var z = outer(); // outer() runs, finishes, and is popped off the Call Stack.
z();
// Output: 10
// Explanation: 'z' now holds the inner() function. When invoked, it still remembers 'a' because it
formed a closure with its lexical environment.
Example 2: Reference vs. Value (The Trick Question)
function outer() {
var a = 10;
function inner() {
[Link](a);
}
a = 100; // We change 'a' before returning
return inner;
}
var z = outer();
z();
// Output: 100
// Explanation: The closure points to the memory reference of 'a', not the static value it had
when inner() was written. Since 'a' was updated to 100 in memory, the closure logs 100.
Practical Uses of Closures
Closures are not just theoretical; they are the backbone of many advanced JavaScript design
patterns and features:
1. Module Design Pattern: Used to encapsulate private variables and functions.
2. Currying: Creating specialized versions of functions (e.g., multiply(x)(y)).
3. Function Once: Creating functions that can strictly be executed only one time.
4. Data Hiding / Encapsulation: Protecting state within a closure so it cannot be modified
directly from the global scope.
5. Timeouts and Event Listeners: Remembering variables when an asynchronous
callback runs later.
Closures and Garbage Collection
While closures are powerful, they must be used carefully because they can lead to memory
consumption issues. Normally, when a function finishes executing, its variables are cleared out
of memory by the browser's Garbage Collector. However, if a closure holds onto references
of outer variables, those variables cannot be garbage collected because they are still being
referenced, which can lead to memory leaks if overused.
💡 The Key Takeaway for Interviews
If an interviewer asks you what a closure is, start with this exact sentence: "A closure is a
function bundled together with its lexical environment, allowing it to access outer scope
variables even after the outer function has returned."