JavaScript10 min read

JavaScript Closures: Understanding Scope

Master JavaScript closures. Understand how functions remember their outer scope and why it matters.

Alex Thompson
December 19, 2025
0.0k0

JavaScript Closures

What is a Closure?

A closure is when a function "remembers" variables from where it was created, even after that place is gone.

Think of it like this: You leave a note for yourself with instructions. Even after you leave the room, the note still has access to information from when you wrote it.

┌─────────────────────┐
│  Outer Function     │
│  creates variable   │
│  count = 0          │
│  ┌───────────────┐  │
│  │  Inner Func   │  │ ← Takes a "snapshot"
│  │  created here │  │   of 'count'
│  │               │  │
│  └───────────────┘  │
└─────────────────────┘
         │
         │ Outer function ends
         │ But inner function
         │ still remembers!
         │
┌────────▼────────────┐
│  Inner function    │
│  still has access  │
│  to 'count'        │
└────────────────────┘

Step-by-Step: Understanding Closures

Step 1: Create outer function

function outer() {
  let count = 0;
}

Step 2: Create inner function inside

function outer() {
  let count = 0;
  
  function inner() {
    count++;
    console.log(count);
  }
}

Step 3: Return the inner function

function outer() {
  let count = 0;
  
  function inner() {
    count++;
    console.log(count);
  }
  
  return inner; // Return the function
}

Step 4: Use it

const counter = outer(); // outer() finishes, but...
counter(); // 1 - inner() still remembers count!
counter(); // 2 - count keeps increasing!
counter(); // 3

The magic: Even though outer() finished running, inner() still remembers and can access count.

Visual Explanation

When outer() runs:
┌─────────────────────┐
│  count = 0          │
│  inner() created   │ ← inner() "sees" count
└─────────────────────┘

When outer() finishes:
┌─────────────────────┐
│  outer() is gone    │
│  BUT...             │
└─────────────────────┘

inner() still has:
┌─────────────────────┐
│  "Memory" of count  │ ← Closure!
│  Can still use it   │
└─────────────────────┘

Why Closures Matter: Data Privacy

Closures let you create "private" variables that can't be accessed from outside:

function createBankAccount(initialBalance) {
  let balance = initialBalance; // Private! Can't access from outside
  
  return {
    deposit: (amount) => {
      balance = balance + amount;
      return balance;
    },
    withdraw: (amount) => {
      if (amount <= balance) {
        balance = balance - amount;
        return balance;
      } else {
        return 'Insufficient funds';
      }
    },
    getBalance: () => balance
  };
}

const account = createBankAccount(100);
account.deposit(50);      // 150
account.withdraw(30);     // 120
account.getBalance();     // 120
// account.balance;        // undefined! Can't access directly

How it works:

┌─────────────────────┐
│  balance = 100      │ ← Private variable
│  (only accessible   │
│   inside closure)   │
│                     │
│  deposit()          │ ← Can access balance
│  withdraw()         │ ← Can access balance
│  getBalance()       │ ← Can access balance
└─────────────────────┘
         │
         │ From outside:
         │ balance is hidden!

Function Factories

Closures let you create functions that are "configured" with specific values:

function createMultiplier(multiplyBy) {
  return function(number) {
    return number * multiplyBy;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

double(5);      // 10 (5 * 2)
triple(5);      // 15 (5 * 3)
quadruple(5);   // 20 (5 * 4)

What happens:

createMultiplier(2) creates:
┌─────────────────────┐
│  Function that      │
│  remembers:         │
│  multiplyBy = 2     │ ← Closure!
└─────────────────────┘

createMultiplier(3) creates:
┌─────────────────────┐
│  Function that      │
│  remembers:         │
│  multiplyBy = 3     │ ← Different closure!
└─────────────────────┘

Each function remembers its own multiplyBy value.

Real Example: Event Handlers

Closures are used everywhere, especially with event handlers:

function setupButton(buttonId, clickCount) {
  const button = document.getElementById(buttonId);
  let count = 0; // Private to this setup
  
  button.addEventListener('click', function() {
    count++;
    console.log(`Button clicked ${count} times`);
    
    if (count >= clickCount) {
      button.disabled = true;
      console.log('Button disabled!');
    }
  });
}

setupButton('myButton', 5);

What happens:

  • Each button gets its own count variable
  • The click handler "remembers" count and clickCount
  • Even though setupButton finished, the handler still works

Common Closure Pattern: Module Pattern

const calculator = (function() {
  let result = 0; // Private
  
  return {
    add: (num) => { result += num; return result; },
    subtract: (num) => { result -= num; return result; },
    multiply: (num) => { result *= num; return result; },
    getResult: () => result,
    reset: () => { result = 0; }
  };
})();

calculator.add(10);      // 10
calculator.multiply(3);  // 30
calculator.getResult();  // 30

This creates a "module" with private variables that can only be accessed through the returned methods.

Quick Tips

  1. Closures happen automatically - Any function that uses an outer variable creates a closure
  2. They're used everywhere - Event handlers, callbacks, modules all use closures
  3. Be careful with loops - Variables in loops can cause unexpected behavior (use let instead of var)
  4. Memory consideration - Closures keep variables in memory, so don't create unnecessary ones

Closures are one of JavaScript's most powerful features. They enable data privacy, function factories, and many common patterns. Once you understand them, you'll see them everywhere in JavaScript code.

#JavaScript#Closures#Scope#Advanced