C
JavaScript/Functions/Lesson 09

Scope + Closure — *The Region Where Variables Live*

1 hr·theory
This chapter
3/4

Scope + Closure — *The Region Where Variables Live*

🎯 After Reading This Lesson

After reading this lesson, you will be able to confidently do the following 3 things.

  • ✅ Define Lexical Scope + Closure with code examples
  • ✅ Create private variables (counters, caches) using Closures
  • ✅ Understand the function-scope trap of var and how to fix it with let

Keep the learning objectives as a checklist and close the lesson once you can answer all of them.

Scope — *Where Can a Variable Be Accessed?*

Key Takeaway

Scope = the range within which a variable is accessible. JS has 3 types of scope: global, function, and block.

3 Types of Scope

javascript
// Global scope — accessible from anywhere
const globalVar = "global";

function outer() {
    // Function scope — only inside outer
    const funcVar = "function";

    if (true) {
        // Block scope — only inside the if block
        let blockVar = "block";
        const alsoBlock = "here too";

// var is function-scoped (ignores blocks)

var varVar = "var is function scope";

}

console.log(funcVar); // ✅

console.log(blockVar); // ❌ ReferenceError

console.log(varVar); // ✅ var is accessible outside the if block

}

code

- **`let`·`const`** — block scope (inside `{}`)
- **`var`** — function scope (another trap with `var`)
- **No declaration keyword** — global (absolutely avoid — a common cause of bugs)

## Scope Chain — *Walking Up to the Outer Scope*

javascript

// 🌍 Global scope

const a = "global";

function outer() {
// 📦 outer function scope (inside global)
const b = "outer";

function inner() {
// 🎯 inner function scope (inside outer)
const c = "inner";

console.log(c); // "inner" ← ① c found in own scope → use directly

console.log(b); // "outer" ← ② b not in own scope → walk up to outer, found

code
        console.log(a);   // "Global"   ← ③ a not in outer → walk up to global, found
    }
    inner();   // 🚀 run inner — the 3 console.log calls above are printed
}

outer();   // ▶️ Execution starts! — without this call, neither outer nor inner ever runs

// 📤 Console output (top to bottom):
//   inner
//   outer
//   Global

When JS looks up a variable:
1. Current scope
2. Outer function scope (if any)
3. Global scope

This chain of lookups is the scope chain.

Closure — A Function That Remembers Its Environment

One-line definition: A function that keeps carrying the variables from where it was created. Analogy — a 🤚 hand (the returned function) that holds onto a 🐷 piggy bank (count).

javascript
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1️⃣ Factory that creates piggy banks (makeCounter)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function makeCounter() {
    // 🐷 Create a new piggy bank (starts with 0 coins)
    let count = 0;

    // 🤚 Create a "hand" that deposits coins and return it
    return function () {
        count++;          // Add 1 coin to the piggy bank
        return count;     // Report the current coin count
    };
    // ↑ This function goes out while "remembering" the count variable above!
    //   → Even after makeCounter finishes, count does not disappear (this is the closure!)
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2️⃣ Receive the piggy bank
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter = makeCounter();
//    ↑          ↑
//    │          └─ Factory runs → produces 🐷 + 🤚 bundle and ships it out
//    └─ Store the received bundle in the counter variable
//       → counter now acts as the "hand that deposits coins into the piggy bank"!

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3️⃣ Deposit coins with the hand (call counter)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
console.log(counter());   // 1   ← 🤚 deposits a coin → 🐷 now: 1
console.log(counter());   // 2   ← deposits again   → 🐷 now: 2 (same piggy bank!)
console.log(counter());   // 3   ← deposits again   → 🐷 now: 3 (keeps accumulating!)

// 💡 Key insight:
//   - A new piggy bank is NOT created each time!
//   - Each counter() call keeps adding coins to the same piggy bank
//   - That is why the count grows: 1, 2, 3

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 4️⃣ What if you need a brand-new piggy bank?
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter2 = makeCounter();   // 🐷 new piggy bank + 🤚 new hand (completely independent!)

console.log(counter2());   // 1   ← new piggy bank, so it starts from 0 again!
console.log(counter2());   // 2
console.log(counter());    // 4   ← the first piggy bank continues from 3!

// 💡 counter and counter2 each hold their own piggy bank → they do not affect each other

🎬 Step-by-step Summary

  • [Step 1] Call makeCounter() — the 🏭 factory creates a 🐷 piggy bank (count=0) and ships out a 🤚 hand that can access it
  • [Step 2] const counter = ... — store the 🤚🐷 bundle in the counter variable. The factory (makeCounter) closes its doors, but since counter is holding the hand, the piggy bank stays alive!
  • [Step 3] Every time counter() is called — 🤚 adds 1 coin to 🐷 and reports the current count

🎯 Closure = "A handle for safely manipulating internal variables from outside"

Benefits: ✅ Variables are not exposed externally (security) ✅ Values persist between calls (state memory) ✅ Each call independently owns its own environment

Practical Use of Closures — Private Variables

javascript
function makeBank(initialBalance) {
    let balance = initialBalance;     // No direct external access

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

const account = makeBank(1000);
account.deposit(500);
account.getBalance();    // 1500
account.balance;         // undefined — direct access not allowed

balance lives inside the function, so it cannot be seen from outside. It can only be manipulated through the defined methods (deposit·withdraw). This is the JS way of encapsulation.

Common Trap — Loops + var

javascript
// ❌ The var trap
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000);
}
// Output: 3, 3, 3 (all share the same i)

// ✅ let — block scope
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000);
}
// Output: 0, 1, 2

var is function-scoped, so all iterations share the same i. let is block-scoped, so each iteration gets a new i.

Quick Summary

  • Scope = the accessible range of a variable
  • let·const use block scope (predictable)
  • Closure = a function that remembers its environment
  • Used for private data and encapsulation

⚡ Try It Yourself — Piggy Bank Closure

🐷 Same piggy bank vs. a new piggy bank. Verify firsthand what it means for a closure to 'remember its own environment.'
✏️ JS 코드
📟 Console output
▶ Press the Run button
⚠️ Runs in a browser sandbox — only console.log() is supported; alert/fetch are not.

🤖 Try Asking AI Like This

Once you know the concepts in this lesson, you can give AI specific instructions. Instead of a vague 'fix this,' you make requests with precise vocabulary — and that is where token savings start.

  • 'Replace all var in this code with const/let'
  • 'Analyze reassignment possibilities and organize with const as the default'
  • 'Diagnose potential hoisting issues in this code'

Why This Reduces Tokens

Without knowing the concepts, you have to ask 'What does that mean?' after receiving an AI reply. That follow-up question is what eats up tokens. Learn the concept once and the conversation ends in one round.

Scope and Closures - JavaScript