Lesson 49: Mastering JavaScript Variable scope, closure with challenges!

manoj ymkmanoj ymk
5 min read

What Is Variable Scope?

Scope determines where variables are accessible.

  • Block scope (let, const): Available only within {}.

  • Function scope (var): Accessible throughout the function.

  • Global scope: Accessible everywhere (not ideal).

{
  let x = 10;
  console.log(x); // ✅ 10
}
console.log(x); // ❌ ReferenceError

What Is a Closure?

A closure is a function that "remembers" the variables from the lexical scope where it was defined — even if it runs elsewhere.

function outer() {
  let counter = 0;
  return function inner() {
    return ++counter;
  };
}
const count = outer();
console.log(count()); // 1
console.log(count()); // 2

Even though outer() is finished, inner() retains access to counter.


Lexical Environment

  • Every execution context (global, function, block) gets a Lexical Environment.

  • It's an internal record with:

    • A list of local bindings (Environment Record)

    • A link to an outer environment ([[Environment]])

When a function is declared, it "closes over" this environment. Hence: closure.


🔁 Scope Lookup Chain

When accessing a variable:

  1. JS looks in the current scope

  2. Then walks outward — via the lexical scope chain — up to global


📸 Diagram: Closure & Lexical Environment (simplified)

makeCounter() call
 └── LexicalEnv: { count: 0 } 
      └── inner() closes over → count

🔹 2. Fill Any Gaps

🧩 Hidden Mechanics & Caveats

  • 🔒 Closures preserve the exact variable, not its value.

      function make() {
        let val = 0;
        return () => ++val;
      }
      const fn = make();
      fn(); // val = 1
    
  • 🧼 Garbage collection is delayed if closures retain the Lexical Environment.

  • ⚠️ Loops with closures:

      let arr = [];
      for (var i = 0; i < 3; i++) {
        arr[i] = function () { return i; };
      }
      console.log(arr[0]()); // 3, not 0!
    

    Why? Because var is function scoped — all closures share the same i.

  • ✅ Fix with let:

      for (let i = 0; i < 3; i++) {
        arr[i] = function () { return i; };
      }
    

🌐 Browser-Specific Gotchas

  • ⚙️ V8 optimization: Unused closure variables may vanish from scope in DevTools.

  • 📉 You might see a surprising undefined when inspecting closures if variable was optimized away.


🧨 Common Mistakes

MistakeWhy It's Wrong
Using var in closuresLeads to shared scope bugs
Assuming closure stores a valueIt stores a reference to a variable
Forgetting memory leaks from retained closuresCan cause performance issues in long-running apps

🔹 3. Challenge Me Deeply

🟢 Basic

  1. Create a closure that returns a function to square a number.

  2. Write a function that returns the next Fibonacci number using closures.

  3. Demonstrate how let and var behave differently in a for loop.

🟡 Intermediate

  1. Make a function once(fn) that allows fn to run only once using closure.

  2. Create a private counter object with inc(), dec(), and value() methods.

  3. Build a timer factory that can start, pause, and reset via closure.

🔴 Advanced

  1. Simulate bind() using closures.

  2. Implement a memoization utility using closures.

  3. Demonstrate how to create a function sandbox with isolated variables.

  4. Implement a publish/subscribe (pub-sub) system using closures for state.

🎯 Bonus Brain-Twister

What does this log and why?

function mystery() {
  const secrets = [];
  for (let i = 0; i < 3; i++) {
    secrets[i] = () => i;
  }
  return secrets;
}
const hidden = mystery();
console.log(hidden[0](), hidden[1](), hidden[2]());

🔹 4. Interview-Ready Questions

Conceptual

  • What is a closure, and why are they useful in JavaScript?

  • How do closures relate to Lexical Environments?

Scenario-Based

  • You're writing an event listener inside a loop — how do you avoid closure pitfalls?

  • How would you implement private data in JavaScript?

Debugging Style

  • A dev says "my counter always logs 3 instead of 0/1/2." Spot the bug in this:
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}

Best Practices

  • ✅ Prefer let over var to avoid accidental closure-sharing.

  • ✅ Use closures for encapsulation (e.g., module pattern).

  • ✅ Watch for leaks in long-lived closures (e.g., in event handlers or timers).


🔹 5. Real-World Usage

🔧 In Frameworks

  • React hooks (like useState): Closures preserve stale state.

  • Redux middleware: Functions that wrap functions — often use closures.

  • Memoization utils: lodash.memoize, useMemo, etc.

💼 Production Patterns

  • Debouncing & throttling

  • Private data encapsulation in classes before #private was standardized

  • Custom hook factories in React


🔹 6. Remember Like a Pro

🧠 Mnemonic

"Closure Captures Context" – CCC

🗺️ Visual Map

Global Scope
 └─ Function (outer)
     └─ Function (inner)
         └─ Captures variables in outer via closure

📝 Cheatsheet

FeatureScope TypeAccessible Where
varFunctionInside whole function
let, constBlockOnly inside {}
ClosureLexicalWhere function was defined, not called

🔹 7. Apply It in a Fun Way

🛠️ Mini Project: "Secret Vault" Closure Game

Goal: Lock some private state in closures.

🧩 Build a utility that:

  1. Creates a vault with a password

  2. Exposes methods like .unlock(password), .read(), .changePassword()

  3. Prevents access if not unlocked

Steps:

  1. Write createVault(secret, password)

  2. Store secret and password inside closure

  3. .read() throws unless unlocked

  4. Support lock() and changePassword()

🔁 Extend: Add expiration timer (vault locks itself in 30s).


➕ Bonus: Performance, Mistakes & Modern Tips

⚠️ Mistakes

  • Forgetting closure keeps reference (not copy)

  • Creating unnecessary closures inside loops

  • Retaining DOM refs via closures → memory leaks

🚀 Performance Tips

  • Don’t overuse closures in hot paths

  • Prefer modules for shared state encapsulation

  • Avoid retaining huge contexts unnecessarily

🛠️ Polyfills / Alternatives

  • Private class fields now replace closure for true encapsulation:

      class Counter {
        #count = 0;
        inc() { return ++this.#count; }
      }
    
0
Subscribe to my newsletter

Read articles from manoj ymk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

manoj ymk
manoj ymk