Author Watermark

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 / Parsing (Planner) Scans declarations (var, let, const, fn) • Sets up Execution Context • Maps Scope Phase 2: Execution (Runner) Runs line-by-line • Assigns values into memory slots • Evaluates operations & outputs

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:

  1. Variable Declarations: Every var, let, and const name across the scope.
  2. Function Declarations: Full functions written with the function keyword.
  3. Scope Boundaries: Maps out where blocks {} and functions begin and end.

The Memory Setup in Phase 1:

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:

  1. Value Assignments: When it hits x = 3, it places the value 3 into the memory slot it already created in Phase 1.
  2. Operations: Runs calculations, loops, conditions, and evaluates expressions.
  3. 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:

  1. It sees var x.
  2. It sets aside a slot of memory in the Variable Environment (part of the Execution Context).
  3. 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:

  1. It reaches the line where you wrote x = 3.
  2. It looks up the memory slot it previously created for x.
  3. It wipes out undefined and writes the primitive value 3 into 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)

  1. JavaScript scans the file and sees let x.
  2. It sets aside a slot of memory for x, just like it did for var.
  3. 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)

  1. JavaScript runs your code line by line.
  2. It reaches the line let x = 3.
  3. JavaScript finally unlocks the memory slot, initializes it, and writes the primitive value 3 directly 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)

  1. JavaScript scans the file and finds const x.
  2. It reserves a slot in memory for x.
  3. The const Rule: Just like let, JavaScript leaves the memory slot completely uninitialized and locks it in the Temporal Dead Zone.
  4. Syntax Check: JavaScript checks if you assigned a value right there (e.g., = 3). If you just wrote const x; without a value, the code crashes instantly during Phase 1 with a SyntaxError: Missing initializer in const declaration.

Phase 2: The Execution Phase (The Permanent Freeze)

  1. JavaScript runs the code line by line and reaches const x = 3.
  2. It unlocks the memory slot, writes the primitive value 3 inside it, and permanently bolts the door shut.
  3. If any line of code later tries to overwrite this slot (such as x = 4), JavaScript immediately throws a TypeError: 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.

JS
console.log(myColor); // Prints "undefined", not an error!
var myColor = "blue";
console.log(myColor); // Prints "blue"

Behind the scenes, JavaScript processes it like this:

JS
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 (var)

If you create a var inside a function, you can use it anywhere in that function, even outside the block where it sits:

JS
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:

JS
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:


5. Redeclaration Rules

var Allows Redeclaration

JS
var x = 3;
console.log(x); // Prints 3
var x = 4;
console.log(x); // Prints 4

Under the Hood:

  1. Creation Phase: JavaScript scans the code. It sees var x twice, but it only creates one single memory slot named x and initializes it as undefined. It ignores the redundant second declaration keyword.
  2. Execution Phase:

let Forbids Redeclaration

JS
let y = 5;
console.log(y);
let y = 6;
console.log(y);

Under the Hood:

  1. Creation Phase: JavaScript scans the code and sees let y. It creates a memory slot for y. Then it sees let y a second time in the exact same scope.
  2. The Crash: JavaScript strictly forbids declaring the same let variable 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.

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.

Memory Location Stack Address Value inside the slot
Variable x 0x001A 00000011 (The number 3 in binary)

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.

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" }

7. What This Looks Like in React Native

In real-world React Native components, every variable is declared with either let or const:

REACT
//  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:

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?

JS
for (var i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

Q2: How does fixing the loop above with let work?

JS
for (let i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}

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:

JS
for (var i = 1; i <= 3; i++) {
  (function(iCopy) {
    setTimeout(function() {
      console.log(iCopy);
    }, 1000);
  })(i); // Pass 'i' into the IIFE
}

Q4: What is the output of the following code, and what concept does it demonstrate?

JS
let count = 50;
function display() {
  console.log(count);
  let count = 100;
}
display();

Q5: Why does const user = { name: "Sam" }; user.name = "Alex"; succeed, while Object.freeze(user); user.name = "John"; behaves differently?