JavaScript Core/
Lesson

ScopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it. answers the question: "Where can I use this variable?" Understanding scope is crucial because it determines the lifetime and visibility of your data. Get scope wrong, and you'll have bugs where variables disappear or unexpectedly share values. Get it right, and you unlock powerful patterns like closures and data privacy.

The three types of scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.

Global scope

Variables declared outside any function or block are in the global scope:

// Global variable
const apiKey = "abc123";

function fetchData() {
  // Can access global variables
  console.log(apiKey); // "abc123"
}

fetchData();
console.log(apiKey); // "abc123" (accessible everywhere)

Global variables are accessible everywhere, which sounds convenient but is actually dangerous. Any part of your code can change them, leading to hard-to-debug issues. Modern best practice is to minimize global variables.

Module scope
In modern JavaScript (ES6 modules), variables declared at the top level of a module are not truly global, they're scoped to that module. This is one of many reasons to use modules instead of script tags.

Function scope

Variables declared inside a function only exist inside that function:

function calculateTotal() {
  const tax = 0.2; // Only exists in this function
  const price = 100;
  return price * (1 + tax);
}

console.log(calculateTotal()); // 120
console.log(tax); // ReferenceError: tax is not defined

Functions are the primary way to organize and encapsulate code in JavaScript. Each function creates a new scope.

Block scope

let and const are block-scoped, they only exist within the curly braces where they're defined:

if (true) {
  const message = "Hello";
  let count = 0;
  console.log(message); // "Hello"
}

console.log(message); // ReferenceError
console.log(count);   // ReferenceError

This includes if statements, loops, and even bare blocks:

{
  const secret = "hidden";
}
console.log(secret); // ReferenceError
Scope typeCreated byvar behaviorlet/const behavior
GlobalTop-level codeAccessible everywhereAccessible everywhere
Functionfunction() {}Confined to functionConfined to function
Blockif, for, {}Leaks out of blockConfined to block

The var problem

Before ES6, JavaScript only had var, which is function-scoped (not block-scoped):

if (true) {
  var oldStyle = "I'm everywhere in this function!";
}

console.log(oldStyle); // "I'm everywhere in this function!"

This "leaks" variables out of blocks and causes confusion. Always use let or const instead.

02

Lexical scopeWhat is lexical scope?The rule that a variable's visibility is determined by where it is written in the source code, not where it is called from.

JavaScript uses lexical scope (also called static scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.), which means scope is determined by where functions are written in the code, not where they're called:

const outer = "I'm outside!";

function innerFunc() {
  console.log(outer); // Can see outside!
}

function wrapper() {
  const outer = "I'm inside wrapper!";
  innerFunc(); // Still logs "I'm outside!"
}

innerFunc(); // "I'm outside!"
wrapper();   // "I'm outside!"

Even though innerFunc is called from inside wrapper, it was defined in the global scope, so it looks for variables there. This is lexical scope in action.

03

Closures

A closureWhat is closure?A function that remembers variables from the surrounding code where it was created, even after that surrounding code has finished running. is a function that remembers the variables from its outer scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it., even after the outer function has finished executing:

function createCounter() {
  let count = 0; // This variable is "closed over"

  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count
  };
}

const counter = createCounter();

console.log(counter.getCount()); // 0
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2

Even though createCounter finished running, the returned functions still have access to count. The variables are "closed over" and preserved.

AI pitfall
AI tools create closure bugs in loops constantly. They'll use var in a for loop and push functions that all reference the same variable, so every function returns the final loop value instead of the value at the time it was created. AI also generates stale closures in React: event handlers or useEffect callbacks that capture an old state value and never see updates. Always check whether AI-generated closures capture the right value at the right time.

Each closure is independent

Each call to createCounter creates a new, separate count:

const counterA = createCounter();
const counterB = createCounter();

counterA.increment();
console.log(counterA.getCount()); // 1
console.log(counterB.getCount()); // 0 (separate!)

Practical closure uses

Data privacy:

function createBankAccount(initialBalance) {
  let balance = initialBalance; // Private variable

  return {
    deposit: (amount) => {
      balance += amount;
      return balance;
    },
    withdraw: (amount) => {
      if (amount > balance) {
        throw new Error("Insufficient funds");
      }
      balance -= amount;
      return balance;
    },
    getBalance: () => balance
  };
}

const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance);      // undefined (can't access directly!)

Function factories:

function makeMultiplier(factor) {
  return (number) => number * factor;
}

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

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

Quick reference

Scope typeVariablesAccessible from
GlobalDeclared outside functionsEverywhere
FunctionDeclared inside functionsInside that function
Blocklet/const in {}Inside that block
Closure patternWhat it doesExample use
Data hidingCreate private variablesBank account, counter
State preservationRemember values between callsCaching, memoization
Factory functionsCreate customized functionsMultipliers, formatters
javascript
// Closure example
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    getCount: () => count
  };
}

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

// Private variables with closure
function createBankAccount(balance) {
  return {
    deposit: (amount) => balance += amount,
    getBalance: () => balance
  };
}

const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150