JavaScript Scope & Execution Context ๐ŸŽฏ

ยท15 min readยทPublish by Mahdi Nasir
JavascriptFront-end

JavaScript Scope & Execution Context ๐ŸŽฏ

Part 1: Scope - Where Variables Live ๐Ÿ 

What is Scope?

Scope determines where variables can be accessed in your code.

Think of it like rooms in a house:

  • You can see what's in your current room
  • You can see into rooms that contain your room
  • You can't see into smaller rooms inside your room

Types of Scope

1. Global Scope ๐ŸŒ

Variables accessible everywhere in your code.

// Global scope - like the entire house const planet = "Earth"; const population = 8000000000; function showPlanet() { console.log(planet); // โœ… Can access global } if (true) { console.log(planet); // โœ… Can access global } console.log(planet); // โœ… Can access global

When to use:

  • Constants used throughout your app
  • Configuration values
  • But be careful! Too many globals = messy code

2. Function Scope ๐Ÿข

Variables only accessible inside the function where declared.

function calculateTax() { // Function scope - like a private office const taxRate = 0.15; var oldStyleTax = 0.10; // var is also function scoped const total = 1000 * taxRate; console.log(total); // โœ… Works inside function return total; } calculateTax(); // Works // Outside the function: console.log(taxRate); // โŒ Error: taxRate is not defined console.log(total); // โŒ Error: total is not defined console.log(oldStyleTax); // โŒ Error: oldStyleTax is not defined

Key point: Both var and let/const are function scoped!


3. Block Scope ๐Ÿ“ฆ

Variables only accessible inside {} blocks (with let and const only).

if (true) { // Block scope - like a small cubicle let blockVar = "I'm trapped in this block"; const alsoBlock = "Me too"; var notBlock = "I escape blocks!"; console.log(blockVar); // โœ… Works inside block } // Outside the block: console.log(blockVar); // โŒ Error: not defined console.log(alsoBlock); // โŒ Error: not defined console.log(notBlock); // โœ… Works! (var ignores block scope)

Block scope works with:

  • if statements
  • for loops
  • while loops
  • Any {...} curly braces
// Block scope in loops for (let i = 0; i < 3; i++) { // 'i' only exists inside this loop console.log(i); // 0, 1, 2 } console.log(i); // โŒ Error: i is not defined // Block scope in random blocks { const secret = "Hidden"; console.log(secret); // โœ… Works } console.log(secret); // โŒ Error

Scope Chain ๐Ÿ”—

JavaScript searches for variables by going up the chain from inner to outer scopes.

// Level 1: Global scope const level1 = "Global"; function outer() { // Level 2: Outer function scope const level2 = "Outer"; function inner() { // Level 3: Inner function scope const level3 = "Inner"; // JavaScript searches: inner โ†’ outer โ†’ global console.log(level3); // โœ… Found in inner (current scope) console.log(level2); // โœ… Found in outer (parent scope) console.log(level1); // โœ… Found in global (grandparent scope) } inner(); // Can't access inner scope from outer console.log(level3); // โŒ Error - can't go DOWN the chain } outer();

How Scope Chain Works:

  1. JavaScript looks in current scope first
  2. If not found โ†’ looks in parent scope
  3. Keeps going up until global scope
  4. If still not found โ†’ Error: not defined

Visual Representation:

Global Scope (level1) ๐ŸŒ
  โ””โ”€โ”€ Outer Function (level2) ๐Ÿข
       โ””โ”€โ”€ Inner Function (level3) ๐Ÿ“ฆ
       
Search direction: ๐Ÿ“ฆ โ†’ ๐Ÿข โ†’ ๐ŸŒ (inner to outer)
NEVER: ๐ŸŒ โ†’ ๐Ÿข โ†’ ๐Ÿ“ฆ (outer to inner)

Lexical Scope (Static Scope) ๐Ÿ“

Scope is determined by WHERE you write the code, not WHERE you call it.

const name = "Global Alice"; function outer() { const name = "Outer Bob"; function inner() { // 'name' is determined by WHERE inner() is WRITTEN // inner() is written inside outer(), so it uses outer's 'name' console.log(name); } return inner; } const name = "Global Charlie"; // This doesn't affect inner() const myFunc = outer(); myFunc(); // Prints "Outer Bob" (NOT "Global Charlie")

Why this matters:

  • Functions "remember" where they were defined
  • This is the foundation of closures
  • Makes code predictable and easier to debug

Shadowing Variables ๐ŸŒ“

When inner scope has same variable name as outer scope:

const message = "Global message"; function showMessage() { const message = "Function message"; // Shadows global 'message' console.log(message); // "Function message" (uses closest scope) if (true) { const message = "Block message"; // Shadows function 'message' console.log(message); // "Block message" } console.log(message); // "Function message" again } showMessage(); console.log(message); // "Global message"

Rule: JavaScript always uses the closest scope when variables have same name.


Part 2: Execution Context - The Environment โš™๏ธ

What is Execution Context?

Execution Context = The environment where JavaScript code is executed.

Think of it as a workspace that JavaScript creates:

  • Stores variables
  • Tracks function calls
  • Manages scope

Types of Execution Context

1. Global Execution Context ๐ŸŒ

Created when your script first runs. There's only ONE global context.

// This code runs in Global Execution Context const globalVar = "I'm global"; let count = 0; function myFunc() { // This will create a Function Execution Context } // Still in Global Execution Context console.log(globalVar);

Global Context contains:

  • Global variables
  • Global functions
  • window object (in browsers)
  • this = window (in browsers)

2. Function Execution Context ๐ŸŽญ

Created every time a function is called. Destroyed when function finishes.

function greet(name) { // New execution context created when called const greeting = "Hello"; console.log(greeting + " " + name); // Context destroyed after this line } greet("Alice"); // Creates context #1, then destroys it greet("Bob"); // Creates context #2, then destroys it greet("Charlie"); // Creates context #3, then destroys it

Each function context contains:

  • Function's local variables
  • Function's parameters
  • Reference to outer scope (scope chain)
  • this value

Execution Context Phases ๐Ÿ”„

Every execution context goes through 2 phases:

Phase 1: Creation Phase (Memory Setup) ๐Ÿง 

JavaScript scans code and sets up memory before executing:

// Creation Phase happens BEFORE this code runs console.log(x); // undefined (not an error!) console.log(greet); // [Function: greet] console.log(y); // โŒ ReferenceError: Cannot access 'y' before initialization var x = 10; function greet() { return "Hello"; } let y = 20;

What happens in Creation Phase:

Declaration TypeWhat HappensCan Access Before Declaration?
varHoisted, set to undefinedโœ… Yes (but value is undefined)
function declarationHoisted completelyโœ… Yes (fully works)
let / constHoisted but not initializedโŒ No (Temporal Dead Zone)
function expressionFollows variable rulesDepends on var/let/const

Hoisting Example:

// What you write: console.log(a); // undefined var a = 5; sayHi(); // "Hi!" function sayHi() { console.log("Hi!"); } // What JavaScript does internally: var a; // Hoisted to top, value = undefined function sayHi() { // Hoisted completely console.log("Hi!"); } console.log(a); // undefined a = 5; sayHi(); // "Hi!"

Temporal Dead Zone (TDZ):

// TDZ for 'x' starts here console.log(x); // โŒ Error: Cannot access before initialization let x = 10; // TDZ ends here console.log(x); // โœ… Works: 10

Phase 2: Execution Phase (Code Runs) โ–ถ๏ธ

JavaScript executes code line by line:

// Execution Phase - runs top to bottom var x = 10; // x gets value 10 let y = 20; // y gets value 20 const z = 30; // z gets value 30 function calculate() { const result = x + y + z; return result; } console.log(x); // 10 console.log(y); // 20 console.log(calculate()); // 60

Call Stack ๐Ÿ“š

JavaScript uses a Call Stack to manage execution contexts.

Call Stack = Stack of execution contexts (LIFO - Last In, First Out)

function first() { console.log("Inside first"); second(); console.log("Back in first"); } function second() { console.log("Inside second"); third(); console.log("Back in second"); } function third() { console.log("Inside third"); } first();

Call Stack Visualization:

Step 1: Script starts
[Global Execution Context]

Step 2: first() called
[Global Execution Context]
[first() Execution Context] โ† Current

Step 3: second() called inside first()
[Global Execution Context]
[first() Execution Context]
[second() Execution Context] โ† Current

Step 4: third() called inside second()
[Global Execution Context]
[first() Execution Context]
[second() Execution Context]
[third() Execution Context] โ† Current

Step 5: third() finishes
[Global Execution Context]
[first() Execution Context]
[second() Execution Context] โ† Back here

Step 6: second() finishes
[Global Execution Context]
[first() Execution Context] โ† Back here

Step 7: first() finishes
[Global Execution Context] โ† Back to global

Step 8: Script ends
[Empty]

Output:

Inside first
Inside second
Inside third
Back in second
Back in first

Practical Example: Bank Account ๐Ÿฆ

// Global Execution Context let globalBalance = 1000; function createBankAccount(customerName) { // createBankAccount Execution Context let balance = globalBalance; // Local copy let transactions = []; function deposit(amount) { // deposit Execution Context let timestamp = new Date(); // Can access (via scope chain): // - timestamp (own scope) // - amount (parameter) // - balance (parent scope) // - transactions (parent scope) // - customerName (parent scope) // - globalBalance (global scope) balance += amount; transactions.push({ type: "Deposit", amount: amount, time: timestamp }); console.log(`${customerName} deposited $${amount}`); console.log(`New balance: $${balance}`); } function withdraw(amount) { // withdraw Execution Context let timestamp = new Date(); if (balance >= amount) { balance -= amount; transactions.push({ type: "Withdraw", amount: amount, time: timestamp }); console.log(`${customerName} withdrew $${amount}`); console.log(`New balance: $${balance}`); } else { console.log("Insufficient funds!"); } } function getStatement() { console.log(`Account holder: ${customerName}`); console.log(`Current balance: $${balance}`); console.log("Transactions:", transactions); } return { deposit, withdraw, getStatement }; } // Using the bank account const aliceAccount = createBankAccount("Alice"); aliceAccount.deposit(500); // Creates deposit() execution context aliceAccount.withdraw(200); // Creates withdraw() execution context aliceAccount.getStatement(); // Creates getStatement() execution context // โŒ Can't access these from outside: console.log(balance); // Error: not defined console.log(transactions); // Error: not defined console.log(customerName); // Error: not defined

When and Why to Use This Knowledge? ๐Ÿค”

1. Avoiding Variable Bugs ๐Ÿ›

BAD - var in loops:

for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Prints: 3, 3, 3 (all share same 'i')

GOOD - let in loops:

for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Prints: 0, 1, 2 (each iteration has its own 'i')

2. Creating Private Variables ๐Ÿ”’

function createCounter() { let count = 0; // Private - can't access from outside return { increment: () => ++count, decrement: () => --count, getCount: () => count }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.count); // undefined (private!)

3. Understanding Hoisting Issues โฌ†๏ธ

// Why this works: sayHi(); // "Hi!" function sayHi() { console.log("Hi!"); } // Why this doesn't: greet(); // โŒ Error: greet is not a function var greet = function() { console.log("Hello!"); }; // Because var greet is hoisted as undefined

4. Debugging Scope Errors ๐Ÿ”ง

function outer() { let x = 10; function inner() { console.log(x); // โœ… Works (scope chain) console.log(y); // โŒ Error (not in scope chain) } let y = 20; inner(); }

Understanding scope helps you:

  • Know where variables are accessible
  • Fix "undefined" vs "not defined" errors
  • Read call stack in debugger
  • Write cleaner, bug-free code

Key Takeaways ๐Ÿ“

Scope:

  1. Global Scope = everywhere accessible
  2. Function Scope = only inside function
  3. Block Scope = only inside {} (let/const only)
  4. Scope Chain = searches inner โ†’ outer
  5. Lexical Scope = determined by code position

Execution Context:

  1. Global Context = created when script starts
  2. Function Context = created on function call
  3. Creation Phase = memory setup (hoisting)
  4. Execution Phase = code runs line by line
  5. Call Stack = manages execution contexts

Best Practices:

  • โœ… Use let and const (block scoped)
  • โŒ Avoid var (function scoped, confusing hoisting)
  • โœ… Declare variables at top of their scope
  • โœ… Use scope for encapsulation/privacy
  • โœ… Understand scope chain for debugging

Memory Trick ๐Ÿง 

Scope = Russian Nesting Dolls ๐Ÿช†

  • Inner dolls can see outer dolls
  • Outer dolls can't see inside inner dolls
  • JavaScript always looks inside โ†’ outside (NEVER outside โ†’ inside)

Execution Context = Workspace ๐Ÿ› ๏ธ

  • Creation Phase = Setting up the workspace
  • Execution Phase = Actually doing the work
  • Call Stack = Stack of workspaces

References

Published on October 01, 2025ยท15 minute read