Chapter 1: let, const & block scoping: why var is gone
Before building interactive user interfaces or managing state updates across screens in React Native, you need a precise mental model of how the JavaScript engine reads, allocates, and runs your code. Understanding the execution lifecycle, the differences between var, let, and const, and how variables reside in memory prevents subtle runtime bugs, scope pollution, and unexpected crashes.
📺 Video Explanation
Watch the full explanation of these notes here: https://youtu.be/xUGv1nLpPxQ
1. How JavaScript Reads Your Code (The Two Execution Phases)
The JavaScript runtime does not read and run your source code in a single, blind pass. It processes code in two distinct phases: the Compilation (Parsing) Phase and the Execution Phase.
Phase 1: Compilation (or Parsing) Phase
During this phase, the JavaScript engine acts like an architect and planner. It ignores all values and only looks for names and structures to create a blueprint in memory called the Execution Context.
It detects three main things:
- Variable Declarations: Every
var,let, andconstname across the scope. - Function Declarations: Full functions written with the
functionkeyword. - Scope Boundaries: Maps out where blocks
{}and functions begin and end.
The Memory Setup in Phase 1:
- For
var: Reserves a memory slot and immediately fills it withundefined. - For
letandconst: Reserves a memory slot but leaves it completely uninitialized (locked). - For
function: Copies the entire function body straight into memory immediately.
If the engine sees two let or const variables with the exact same name in the same scope during this phase, it stops right here, cancels the plan, and throws a SyntaxError before running a single line of code.
Phase 2: The Execution Phase
If Phase 1 finishes with no syntax errors, JavaScript starts the Execution Phase. This is where the code actually runs line-by-line from top to bottom:
- Value Assignments: When it hits
x = 3, it places the value3into the memory slot it already created in Phase 1. - Operations: Runs calculations, loops, conditions, and evaluates expressions.
- Output: Triggers external actions like
console.log()or bridge updates.
2. What Happens in Memory (Under the Hood)
When you write a single declaration line, JavaScript splits it into two separate operations behind the scenes: Creation and Execution.
What Happens with var x = 3
Step 1: The Creation Phase (Hoisting)
Before a single line of code runs, JavaScript scans the file:
- It sees
var x. - It sets aside a slot of memory in the Variable Environment (part of the Execution Context).
- It automatically fills that memory slot with the primitive value
undefined.
At this exact moment, x exists in memory, but its value is undefined.
Step 2: The Execution Phase (Assignment)
Now, JavaScript starts running your code line-by-line from top to bottom:
- It reaches the line where you wrote
x = 3. - It looks up the memory slot it previously created for
x. - It wipes out
undefinedand writes the primitive value3into that memory slot.
What Happens with let x = 3 (The Lockout & TDZ)
When you use let x = 3, the memory process changes in one critical way to protect your code from silent bugs:
Step 1: The Creation Phase (The Lockout)
- JavaScript scans the file and sees
let x. - It sets aside a slot of memory for
x, just like it did forvar. - The Big Difference: JavaScript does not initialize it with
undefined. Instead, it leaves the memory slot completely uninitialized and marks it as inaccessible.
This locked state is called the Temporal Dead Zone (TDZ). If you try to read x before its declaration line, JavaScript throws a ReferenceError because the memory slot is locked.
Step 2: The Execution Phase (The Unlock)
- JavaScript runs your code line by line.
- It reaches the line
let x = 3. - JavaScript finally unlocks the memory slot, initializes it, and writes the primitive value
3directly into it. Now, the variable is safely born and ready to use.
What Happens with const x = 3 (The Permanent Freeze)
When you write const x = 3, JavaScript sets up a memory slot for x that is permanently locked with the value 3. Unlike var or let, you must give a const variable its value immediately on the same line, and you can never reassign that value later.
Phase 1: The Creation Phase (The Lockout & Syntax Check)
- JavaScript scans the file and finds
const x. - It reserves a slot in memory for
x. - The
constRule: Just likelet, JavaScript leaves the memory slot completely uninitialized and locks it in the Temporal Dead Zone. - Syntax Check: JavaScript checks if you assigned a value right there (e.g.,
= 3). If you just wroteconst x;without a value, the code crashes instantly during Phase 1 with aSyntaxError: Missing initializer in const declaration.
Phase 2: The Execution Phase (The Permanent Freeze)
- JavaScript runs the code line by line and reaches
const x = 3. - It unlocks the memory slot, writes the primitive value
3inside it, and permanently bolts the door shut. - If any line of code later tries to overwrite this slot (such as
x = 4), JavaScript immediately throws aTypeError: Assignment to constant variable.
3. Hoisting Mechanics Compared
Hoisting is how JavaScript reserves variable and function declarations in memory before any code runs.
How var Behaves with Hoisting
When you use var, JavaScript "lifts" the declaration to the top of the function or global scope, initializing it with undefined until execution reaches the actual line of code.
console.log(myColor); // Prints "undefined", not an error!
var myColor = "blue";
console.log(myColor); // Prints "blue"
Behind the scenes, JavaScript processes it like this:
var myColor; // Moved to top (initialized as undefined)
console.log(myColor); // undefined
myColor = "blue"; // Value assigned here
console.log(myColor); // "blue"
let and const Hoisting
let and const are also hoisted (the engine registers their identifiers during Phase 1), but JavaScript does not initialize them with undefined. If you try to use them before the line where they are declared, JavaScript throws a ReferenceError. The window between scope entry and declaration is the Temporal Dead Zone (TDZ).
4. Scope Rules: Function Scope vs. Block Scope
Scope defines where a variable is accessible during runtime execution.
- Function scope: A variable is visible anywhere inside the entire function where it was created.
varworks this way. - Block scope: A variable is visible only inside the specific pair of curly braces
{}(such as anifstatement orforloop) where it was created.letandconstwork this way.
Function Scope (var)
If you create a var inside a function, you can use it anywhere in that function, even outside the block where it sits:
function test() {
if (true) {
var message = "Hello";
}
console.log(message); // Works! Prints "Hello"
}
Why? The variable "escapes" the if block because var only respects the boundary of the enclosing function.
Block Scope (let / const)
If you create a let or const inside curly braces {}, it is locked inside those braces and cannot escape:
function test() {
if (true) {
let message = "Hello";
}
console.log(message); // Error! message is not defined
}
Why? The variable ceases to exist outside that block the moment execution leaves the closing brace }.
Summary Checklist:
var= Function scope (ignores{}blocks, stays inside the enclosing function).let/const= Block scope (trapped strictly inside{}blocks).
5. Redeclaration Rules
var Allows Redeclaration
var x = 3;
console.log(x); // Prints 3
var x = 4;
console.log(x); // Prints 4
Under the Hood:
- Creation Phase: JavaScript scans the code. It sees
var xtwice, but it only creates one single memory slot namedxand initializes it asundefined. It ignores the redundant second declaration keyword. - Execution Phase:
- It stores
3in thexmemory slot $\rightarrow$ logs3. - It overwrites the same memory slot with
4$\rightarrow$ logs4.
let Forbids Redeclaration
let y = 5;
console.log(y);
let y = 6;
console.log(y);
Under the Hood:
- Creation Phase: JavaScript scans the code and sees
let y. It creates a memory slot fory. Then it seeslet ya second time in the exact same scope. - The Crash: JavaScript strictly forbids declaring the same
letvariable twice in the same scope block.
Before running even the first line of code, JavaScript detects this duplicate name and throws a SyntaxError: Identifier 'y' has already been declared. Because compilation fails, console.log(y) never runs.
6. Memory Storage: Primitives vs. Objects with const
JavaScript handles data storage differently depending on whether you are storing a primitive value or a complex reference type.
- Object: Stores data in key-value pairs using curly braces
{}(e.g.,const user = { name: "Sam" }). The label (name) points to the value ("Sam"). - Array: Stores an ordered list of items using square brackets
[](e.g.,const colors = ["red", "blue"]). Each item sits at a numbered index starting from0.
1. Primitives (const x = 3)
For a simple primitive, JavaScript places the raw value directly inside the stack memory slot mapped to the variable name.
- Environment Record Map: Maps
"x"to Stack Address0x001A.
| Memory Location | Stack Address | Value inside the slot |
|---|---|---|
Variable x |
0x001A |
00000011 (The number 3 in binary) |
- The
constLock: The lock is placed directly on stack address0x001A. You cannot overwrite the binary bits inside this slot with a new number.
2. Objects (const user = { name: "Sam" })
Objects cannot fit into a standard fixed-size stack slot. JavaScript uses two areas of memory: the Stack and the Heap.
- Environment Record Map: Maps
"user"to Stack Address0x002B.
| Memory Location | Address | Value inside the slot |
|---|---|---|
Variable user (Stack) |
0x002B |
0x9FFF (Hexadecimal pointer address) |
| The Object Data (Heap) | 0x9FFF |
Key-value pair data: { name: "Sam" } |
- The
constLock: The lock is placed only on the Stack slot0x002B. - Mutating Properties: When you run
user.name = "Alex", JavaScript follows the pointer to0x9FFFand updates the data over in the Heap. The slot0x002Bstill holds0x9FFF. Theconstlock is untouched and the operation succeeds. - Reassigning the Object: If you write
user = { name: "John" }, you are attempting to overwrite0x9FFFinside stack slot0x002Bwith a new address. Theconstlock blocks this assignment and throws aTypeError.
7. What This Looks Like in React Native
In real-world React Native components, every variable is declared with either let or const:
// ✅ Standard RN component — every variable is let or const
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
const HomeScreen = () => {
const title = "Today's Steps"; // never reassigned → const
const [count, setCount] = useState(0); // state variable → const (useState handles mutation)
const handlePress = () => {
let updatedCount = count + 1; // temporary value inside a function → let
setCount(updatedCount);
};
return (
<View>
<Text>{title}</Text>
<Text>{count}</Text>
<Button title="Add Step" onPress={handlePress} />
</View>
);
};
export default HomeScreen;
Observations:
const HomeScreen— The component reference itself never gets reassigned.const title— A string that never changes.const [count, setCount]—useStatereturns a constant tuple reference; React manages the state value internally across render cycles.let updatedCount— A short-lived, local computation variable insidehandlePress.
You will never see var in a well-maintained React Native codebase. ESLint's no-var rule is enabled by default in React Native templates.
The Decision Rule
When writing React Native code, follow this rule:
$$\text{Is this value ever going to be reassigned?} \longrightarrow \begin{cases} \textbf{No} & \longrightarrow \textbf{const} \quad \text{(default choice)} \ \textbf{Yes} & \longrightarrow \textbf{let} \end{cases}$$
Start with const. Downgrade to let only when you need reassignment. Never use var.
Technical Interview Questions
Q1: What will this code print out after 1 second?
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
- Output: Prints
4three times in a row (4,4,4). - Why it happens with
var: Becausevaris function-scoped, there is only one shared variableifor the entire loop. When the 1-second timer finishes, the loop has already completed running andihas incremented to4. All three timeout callbacks reference that exact same sharedi.
Q2: How does fixing the loop above with let work?
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
- Output: Prints
1,2, and3. - Why it works with
let: Becauseletis block-scoped, JavaScript creates a brand-new, separateivariable binding for every single loop iteration. Each timeout callback closes over and retains its own specifici.
Q3: How do you fix the var loop using an IIFE (Immediately Invoked Function Expression)?
An IIFE is a function that is defined and executed immediately. It fixes the var loop by creating an isolated function scope for every iteration, capturing the current value of i:
for (var i = 1; i <= 3; i++) {
(function(iCopy) {
setTimeout(function() {
console.log(iCopy);
}, 1000);
})(i); // Pass 'i' into the IIFE
}
- Creates a new scope: Every time the loop runs, the IIFE executes immediately and creates a brand-new function scope.
- Captures the value: The current value of
iis passed as an argument and assigned to parameteriCopy. - Saves the number: Each timeout function closes over its own private
iCopyrather than the shared outeri.
Q4: What is the output of the following code, and what concept does it demonstrate?
let count = 50;
function display() {
console.log(count);
let count = 100;
}
display();
- Output: Throws
ReferenceError: Cannot access 'count' before initialization. - Why: During Phase 1, the inner
let count = 100declaration is hoisted to the top ofdisplay()'s block scope, shadowing the outercount = 50. From the beginning ofdisplay()until thelet count = 100line is reached, the local variable is trapped in the Temporal Dead Zone (TDZ). Callingconsole.log(count)inside the TDZ triggers aReferenceError.
Q5: Why does const user = { name: "Sam" }; user.name = "Alex"; succeed, while Object.freeze(user); user.name = "John"; behaves differently?
- Answer:
constonly locks the reference pointer stored in the Stack (0x002B), ensuring the variable cannot be pointed at a different object address in the Heap. It does not protect the properties stored in Heap memory (0x9FFF). To prevent mutating object properties in the Heap, you must explicitly useObject.freeze(user).