Scope & Closures

Local vs global scope and closures

Scope in JavaScript

Scope determines where variables are accessible in your code. Understanding scope is crucial for avoiding bugs and writing clean code.

Types of Scope

Global Scope

Variables declared outside any function or block are global—accessible everywhere:

const globalVar = "I'm global";

function showGlobal() {
  console.log(globalVar);  // Accessible here
}

showGlobal();  // "I'm global"
console.log(globalVar);  // "I'm global"

Function Scope

Variables declared inside a function are only accessible within that function:

function myFunction() {
  const functionVar = "I'm local to this function";
  console.log(functionVar);  // Works
}

myFunction();
// console.log(functionVar);  // Error: functionVar is not defined

Block Scope

let and const are block-scoped (confined to {}):

if (true) {
  const blockVar = "I'm block-scoped";
  let anotherBlock = "Me too";
  console.log(blockVar);  // Works here
}

// console.log(blockVar);  // Error: blockVar is not defined

for (let i = 0; i < 3; i++) {
  console.log(i);  // 0, 1, 2
}
// console.log(i);  // Error: i is not defined

Scope Chain

JavaScript looks for variables from innermost to outermost scope:

const global = "global";

function outer() {
  const outerVar = "outer";
  
  function inner() {
    const innerVar = "inner";
    
    // Can access all three
    console.log(innerVar);  // "inner"
    console.log(outerVar);  // "outer"
    console.log(global);    // "global"
  }
  
  inner();
  // console.log(innerVar);  // Error: can't access inner scope
}

outer();

Closures

A closure is a function that remembers variables from its outer scope, even after that scope has executed:

function createCounter() {
  let count = 0;  // Private variable
  
  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.decrement());  // 1
console.log(counter.getCount());   // 1

// count is private—can't access it directly
// console.log(counter.count);  // undefined

Practical Closure Examples

// Example 1: Private data
function bankAccount(initialBalance) {
  let balance = initialBalance;
  
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount <= balance) {
        balance -= amount;
        return balance;
      }
      return "Insufficient funds";
    },
    getBalance() {
      return balance;
    }
  };
}

const myAccount = bankAccount(1000);
console.log(myAccount.deposit(500));    // 1500
console.log(myAccount.withdraw(200));   // 1300
console.log(myAccount.getBalance());    // 1300

// Example 2: Function factory
function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

⚠️ Common Pitfalls

Closures in loops can be tricky:

// Problem: Using var in a loop
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // 3, 3, 3 (not 0, 1, 2!)
  }, 1000);
}

// Solution 1: Use let (block-scoped)
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // 0, 1, 2
  }, 1000);
}

// Solution 2: Create a closure with IIFE
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);  // 0, 1, 2
    }, 1000);
  })(i);
}

✓ Best Practices

  • • Use const and let for block scoping
  • • Avoid polluting the global scope
  • • Use closures to create private variables
  • • Be aware of closure memory implications (they keep outer variables alive)
  • • Use closures for data encapsulation and module patterns