JavaScript Variables: Memory, Execution Context, and First Principles
To master JavaScript, one must understand exactly what happens in memory and the execution context when a variable is declared and assigned. It's not just about var, let, and const; it's about hoisting, lexical environments, and the underlying memory heap.
The Execution Context
Whenever JavaScript runs, it runs inside an Execution Context. The Global Execution Context is created by default, and a new Function Execution Context is created every time a function is invoked.
An Execution Context consists of two phases:
- Creation Phase (Memory Allocation): The engine scans the code, sets up the memory space for variables and functions (Hoisting), and establishes the Lexical Environment and Scope Chain.
- Execution Phase (Code Execution): The engine runs the code line-by-line, assigning values to variables and executing function calls.
Hoisting and The Temporal Dead Zone (TDZ)
Hoisting is the mechanism where variable and function declarations are moved into memory during the creation phase before execution starts.
var: Hoisted and initialized withundefined. You can access it before its declaration, but it will beundefined.letandconst: Hoisted but not initialized. Attempting to access them before their declaration results in aReferenceError. The period between entering the scope and the actual declaration is called the Temporal Dead Zone (TDZ).
console.log(a); // undefined
var a = 10;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20; // TDZ ends here
var vs let vs const
| Keyword | Scope | Re-assignable | Hoisted | Initialized during Hoisting | Global Object Property |
| :--- | :--- | :--- | :--- | :--- | :--- |
| var | Function / Global | Yes | Yes | undefined | Yes (window.x) |
| let | Block ({}) | Yes | Yes | No (TDZ) | No |
| const | Block ({}) | No | Yes | No (TDZ) | No |
Note on const: const prevents reassignment of the variable binding. However, if the variable points to an object or array, the contents of that object/array can still be mutated.
Memory Management: Stack vs. Heap
JavaScript variables are stored in either the Stack or the Heap depending on their type.
Primitive Types (Stored on the Stack)
Strings, Numbers, Booleans, null, undefined, Symbols, and BigInts are immutable and have a fixed size. They are stored directly on the Call Stack.
When you assign a primitive to another variable, a true copy of the value is created in memory.
let x = 10;
let y = x; // y is a copy of x
y = 20;
console.log(x); // 10 (x is unaffected)
Reference Types (Stored on the Heap)
Objects, Arrays, and Functions are dynamic and can grow in size. Their actual data is stored in the Memory Heap. The variable on the Stack only holds a reference (memory address) pointing to the location in the Heap.
When you assign a reference type to another variable, only the reference is copied, not the underlying data.
const obj1 = { name: "Alice" };
const obj2 = obj1; // obj2 points to the SAME memory address as obj1
obj2.name = "Bob";
console.log(obj1.name); // "Bob" (both variables mutated the same heap object)
Summary
Understanding how variables tie into the Execution Context and the memory model is vital. It explains the nuances of closures, preventing unintended mutations of reference types, and understanding scope-related bugs caused by the TDZ or var hoisting.