← JS Mastery | Module 3: Functions Scope, Hoisting & the Call Stack
Module 3

Scope, Hoisting & the Call Stack

⏱ 22 min read ● Intermediate 🆓 Free

Understanding Scope

Scope determines where variables are accessible. JavaScript has three types: global, function, and block scope. Understanding scope prevents one of the most common categories of bugs.

// Global scope — accessible everywhere
const globalVar = "I'm global";

function myFunction() {
  // Function scope — only inside this function
  const functionVar = "I'm function-scoped";
  
  if (true) {
    // Block scope — only inside this if block
    const blockVar = "I'm block-scoped";
    let alsoBlock = "Also block-scoped";
    var leaksOut = "I leak to function scope!"; // var = function scope
    
    console.log(globalVar);     // ✅ accessible
    console.log(functionVar);   // ✅ accessible
    console.log(blockVar);      // ✅ accessible
  }
  
  console.log(globalVar);       // ✅ accessible
  console.log(functionVar);     // ✅ accessible
  // console.log(blockVar);     // ❌ ReferenceError
  console.log(leaksOut);        // ✅ var leaks to function scope!
}

// console.log(functionVar);    // ❌ ReferenceError
// console.log(leaksOut);       // ❌ ReferenceError (function-scoped)

Scope Chain & Lexical Scope

// Inner scopes can access outer scope (scope chain):
const outerVar = "outer";

function outer() {
  const middleVar = "middle";
  
  function inner() {
    const innerVar = "inner";
    
    // Can access ALL outer variables:
    console.log(outerVar);   // ✅ "outer"
    console.log(middleVar);  // ✅ "middle"
    console.log(innerVar);   // ✅ "inner"
  }
  
  inner();
  // console.log(innerVar);  // ❌ can't look inward
}

// Variable shadowing:
const x = "global";
function shadow() {
  const x = "local";  // shadows the global x
  console.log(x);  // "local"
}
shadow();
console.log(x);  // "global" — global unchanged

Hoisting

// Function declarations are hoisted COMPLETELY:
sayHello();  // ✅ Works — "Hello!"
function sayHello() { console.log("Hello!"); }

// var declarations are hoisted (but NOT their values):
console.log(myVar);  // undefined (not an error!)
var myVar = "value";
console.log(myVar);  // "value"

// let and const are NOT accessible before declaration:
// console.log(myLet);  // ❌ ReferenceError: Cannot access before init
let myLet = "value";

// Temporal Dead Zone (TDZ):
// let and const variables exist in TDZ from start of scope
// until declaration is reached. Accessing in TDZ = error.

// Function expression — NOT hoisted:
// greet();  // ❌ TypeError: greet is not a function
const greet = function() { console.log("Hi"); };
greet();  // ✅ Works after declaration

The Call Stack

// The call stack tracks function calls
function third() {
  console.trace();  // shows the call stack
  return "third";
}
function second() {
  return third();
}
function first() {
  return second();
}
first();
// Call stack (bottom to top):
// first → second → third

// Stack overflow — too many nested calls:
function infinite() {
  return infinite();  // calls itself forever
}
// infinite();  // ❌ RangeError: Maximum call stack size exceeded

// This is why recursion needs a base case:
function countdown(n) {
  if (n <= 0) return;  // base case — stops recursion
  console.log(n);
  countdown(n - 1);
}
countdown(5);  // 5, 4, 3, 2, 1

⚡ Key Takeaways

🎯 Practice Exercises

EXERCISE 1

Predict the output before running: declare a variable with var inside an if block, then log it outside. Do the same with let. Explain the difference.

EXERCISE 2

Create a nested function structure 3 levels deep. Show that the innermost function can read variables from all outer scopes but outer scopes can't read inner variables.

← Arrow Functions