|

JavaScript 17 🧬 closure

A closure is a function that retains access to its lexical scope, even when the function is executed outside of that scope. This means a closure can “remember” and access variables from the environment in which it was created — even after that environment has finished executing.


A Quick Look at the Examples

// 1. Basic closure — counter
function createCounter() {
    let count = 0;

    return function() {
        count += 1;
        console.log(count);
    };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

// 2. Closure with encapsulation — person
function createPerson(name) {
    let age = 0; // Private variable

    return {
        getName: function() {
            return name;
        },
        getAge: function() {
            return age;
        },
        setAge: function(newAge) {
            if (newAge >= 0) {
                age = newAge;
            }
        }
    };
}

const person = createPerson('John');

console.log(person.getName()); // "John"
console.log(person.getAge());  // 0
person.setAge(30);
console.log(person.getAge());  // 30

a. Closure Introduction

A closure is a function that retains access to its lexical scope, even when the function is executed outside of that scope.

This means a closure can “remember” and access variables from the environment in which it was created — even after that environment has finished executing.

Key Concepts

1. Lexical Scope

Lexical scope refers to the way variables are scoped according to their location in the code, rather than when they are called.

A function’s lexical environment is determined by where it was defined, not where it was executed.

function outer() {
    let message = "Hello"; // Lexically scoped to outer

    function inner() {
        console.log(message); // inner "remembers" message
    }

    return inner;
}

const myFunc = outer();
myFunc(); // "Hello" — message is still accessible!

2. Execution Context

The execution context is the environment in which JavaScript code executes. It includes the variable and function declarations that are currently accessible.

3. Closure Creation

When a function is created inside another function, the inner function retains access to the variables of the outer function’s scope.

┌─────────────────────────────────────┐
│  outer() execution context          │
│  ┌─────────────────────────────┐    │
│  │  let count = 0;             │    │
│  │                             │    │
│  │  ┌─────────────────────┐    │    │
│  │  │  inner() closure    │    │    │
│  │  │  → accesses count   │    │    │
│  │  └─────────────────────┘    │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

Common Misconception

⚠️ “Closures capture values, not references” — This is a common misunderstanding!

Closures capture variables (by reference), not values. Changes to the original variable after the closure is created do affect the closure.

function outer() {
    let count = 0;

    function increment() {
        count++;
        console.log(count);
    }

    function getCount() {
        return count;
    }

    // Modify count from outside
    count = 100;

    return { increment, getCount };
}

const { increment, getCount } = outer();
console.log(getCount()); // 100 — not 0!
increment(); // 101 — closure sees the updated value

What closures actually capture: The variable binding (reference to the variable), not a snapshot of its value at creation time.

Conclusion

Closures enable encapsulation, data hiding, and state management — making them an essential tool for any JavaScript developer.


b. Closure Examples

Example 1: Counter (Basic Closure)

function createCounter() {
    let count = 0;

    return function() {
        count += 1;
        console.log(count);
    };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

Step-by-step:

StepAction
1. Function DefinitioncreateCounter is defined with a local variable count = 0
2. Inner FunctionAn anonymous function is defined inside and returned
3. Closure CreationWhen createCounter() is called, it returns the inner function
4. Retained AccessThe inner function retains access to count from its lexical scope
5. UsageEach call to counter() increments and logs the retained count

Key insight: count persists between calls, even though createCounter has finished executing!


Example 2: Encapsulation and Data Hiding

function createPerson(name) {
    let age = 0; // Private variable

    return {
        getName: function() {
            return name;
        },
        getAge: function() {
            return age;
        },
        setAge: function(newAge) {
            if (newAge >= 0) {
                age = newAge;
            }
        }
    };
}

const person = createPerson('John');

console.log(person.getName()); // "John"
console.log(person.getAge());  // 0
person.setAge(30);
console.log(person.getAge());  // 30
person.setAge(-5);              // Ignored — invalid
console.log(person.getAge());  // 30 (unchanged)

Key insight: age and name are private — not directly accessible from outside. They can only be read or modified through the returned methods.

console.log(person.age);   // undefined — private!
console.log(person.name);  // undefined — private!

Use Cases for Closures

Use CaseDescriptionExample
Encapsulation & Data HidingCreate private variables not accessible from outsidecreatePerson above
Factory FunctionsGenerate objects with specific behaviorcreateCounter, createPerson
Maintaining State in Async OperationsRetain access to variables across async callsSee example below
Event HandlersRemember data specific to each handlerSee example below
Partial Application / CurryingCreate specialized functions from general onesSee example below

Async Example:

function fetchData(url) {
    let attempts = 0;

    return function retry() {
        attempts++;
        console.log(`Attempt ${attempts} for ${url}`);
        // fetch(url)... — attempts is retained across retries
    };
}

const retryFetch = fetchData("https://api.example.com/data");
retryFetch(); // Attempt 1
retryFetch(); // Attempt 2

Event Handler Example:

function attachHandlers() {
    for (let i = 1; i <= 3; i++) {
        document.getElementById(`btn-${i}`).addEventListener('click', function() {
            console.log(`Button ${i} clicked`); // Each closure remembers its own i
        });
    }
}

Partial Application Example:

function multiply(a) {
    return function(b) {
        return a * b;
    };
}

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

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

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Closures</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background: #f8f9fa;
            color: #333;
            line-height: 1.6;
        }
        h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
        h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
        .demo-box {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin: 15px 0;
        }
        pre {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
        }
        .keyword { color: #569cd6; }
        .string { color: #ce9178; }
        .number { color: #b5cea8; }
        .function { color: #dcdcaa; }
        .comment { color: #6a9955; }
        .boolean { color: #569cd6; }
        #output {
            background: #e9ecef;
            padding: 15px;
            border-radius: 8px;
            margin-top: 15px;
            min-height: 40px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th { background: #007bff; color: white; }
        tr:nth-child(even) { background: #f8f9fa; }
        .btn {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 1em;
            font-weight: bold;
            cursor: pointer;
            margin: 5px;
            transition: all 0.3s;
        }
        .btn:hover {
            background: #0056b3;
            transform: translateY(-2px);
        }
        .btn-success { background: #28a745; }
        .btn-success:hover { background: #1e7e34; }
        .btn-danger { background: #dc3545; }
        .btn-danger:hover { background: #a71d2a; }
        .counter-display {
            font-size: 3em;
            font-weight: bold;
            color: #007bff;
            text-align: center;
            padding: 20px;
            font-family: 'Courier New', monospace;
        }
    </style>
</head>
<body>

    <h1>Closures</h1>

    <div class="demo-box">
        <h2>1. Basic Closure — Counter</h2>
        <pre>
<span class="keyword">function</span> <span class="function">createCounter</span>() {
    <span class="keyword">let</span> count = <span class="number">0</span>;

    <span class="keyword">return</span> <span class="keyword">function</span>() {
        count += <span class="number">1</span>;
        console.log(count);
    };
}

<span class="keyword">const</span> counter = <span class="function">createCounter</span>();
counter(); <span class="comment">// 1</span>
counter(); <span class="comment">// 2</span>
counter(); <span class="comment">// 3</span>
        </pre>
    </div>

    <div class="demo-box">
        <h2>2. Interactive Counter</h2>
        <p>Each click increments the private <code>count</code> variable — it persists between calls!</p>
        <div class="counter-display" id="counterDisplay">0</div>
        <button class="btn btn-success" onclick="incrementCounter()">+ Increment</button>
        <button class="btn btn-danger" onclick="resetCounter()">Reset (new closure)</button>
    </div>

    <div class="demo-box">
        <h2>3. Encapsulation — Private Variables</h2>
        <pre>
<span class="keyword">function</span> <span class="function">createPerson</span>(name) {
    <span class="keyword">let</span> age = <span class="number">0</span>; <span class="comment">// Private</span>

    <span class="keyword">return</span> {
        getName: <span class="keyword">function</span>() { <span class="keyword">return</span> name; },
        getAge:  <span class="keyword">function</span>() { <span class="keyword">return</span> age; },
        setAge:  <span class="keyword">function</span>(newAge) {
            <span class="keyword">if</span> (newAge >= <span class="number">0</span>) age = newAge;
        }
    };
}
        </pre>
    </div>

    <div class="demo-box">
        <h2>4. Interactive Person</h2>
        <div style="text-align: center; padding: 20px;">
            <p>Name: <strong id="personName">—</strong></p>
            <p>Age: <strong id="personAge">—</strong></p>
            <button class="btn" onclick="changeAge(1)">+1 Year</button>
            <button class="btn btn-success" onclick="changeAge(10)">+10 Years</button>
            <button class="btn btn-danger" onclick="tryInvalidAge()">Try Invalid (-5)</button>
        </div>
    </div>

    <div class="demo-box">
        <h2>5. Closure Use Cases</h2>
        <table>
            <tr>
                <th>Use Case</th>
                <th>Description</th>
            </tr>
            <tr>
                <td><strong>Data Hiding</strong></td>
                <td>Private variables not accessible from outside</td>
            </tr>
            <tr>
                <td><strong>Factory Functions</strong></td>
                <td>Generate objects with specific behavior</td>
            </tr>
            <tr>
                <td><strong>Partial Application</strong></td>
                <td>Create specialized functions from general ones</td>
            </tr>
            <tr>
                <td><strong>Event Handlers</strong></td>
                <td>Remember data specific to each handler</td>
            </tr>
            <tr>
                <td><strong>Async State</strong></td>
                <td>Retain variables across asynchronous operations</td>
            </tr>
        </table>
    </div>

    <div class="demo-box">
        <h2>6. Live Output — Closure Examples</h2>
        <div id="output">Loading...</div>
    </div>

    <script>
        // ============================================
        // Closures — Live Demo
        // ============================================

        let results = [];

        // 1. Basic counter
        results.push('📌 Basic Counter Closure:\n');

        function createCounter() {
            let count = 0;

            return function() {
                count += 1;
                return count;
            };
        }

        const counter = createCounter();
        results.push('  counter() → ' + counter());
        results.push('  counter() → ' + counter());
        results.push('  counter() → ' + counter());
        results.push('');

        // 2. Independent closures
        results.push('📌 Independent Closures:\n');
        const counterA = createCounter();
        const counterB = createCounter();

        results.push('  counterA() → ' + counterA()); // 1
        results.push('  counterA() → ' + counterA()); // 2
        results.push('  counterB() → ' + counterB()); // 1 (separate!)
        results.push('  counterA() → ' + counterA()); // 3
        results.push('');

        // 3. Encapsulation
        results.push('📌 Encapsulation — Private Variables:\n');

        function createPerson(name) {
            let age = 0;

            return {
                getName: function() { return name; },
                getAge: function() { return age; },
                setAge: function(newAge) {
                    if (newAge >= 0) age = newAge;
                }
            };
        }

        const person = createPerson('John');
        results.push('  person.getName() → ' + person.getName());
        results.push('  person.getAge() → ' + person.getAge());
        person.setAge(30);
        results.push('  After setAge(30): person.getAge() → ' + person.getAge());
        person.setAge(-5);
        results.push('  After setAge(-5): person.getAge() → ' + person.getAge() + ' (invalid ignored)');
        results.push('  person.age → ' + person.age + ' (private — not accessible!)');
        results.push('');

        // 4. Partial application
        results.push('📌 Partial Application (Currying):\n');

        const multiply = a => b => a * b;
        const double = multiply(2);
        const triple = multiply(3);

        results.push('  const double = multiply(2)');
        results.push('  double(5) → ' + double(5));
        results.push('  triple(5) → ' + triple(5));
        results.push('');

        // 5. Loop with let (block scope closure)
        results.push('📌 Closures in Loops (with let):\n');

        const functions = [];
        for (let i = 1; i <= 3; i++) {
            functions.push(() => i);
        }

        results.push('  for (let i = 1; i <= 3; i++) { functions.push(() => i); }');
        results.push('  Each closure remembers its own i:');
        functions.forEach((fn, idx) => {
            results.push('    functions[' + idx + ']() → ' + fn());
        });
        results.push('');

        // 6. Closures capturing references (not values)
        results.push('📌 Closures Capture References (not values):\n');

        function outer() {
            let value = 10;

            function show() {
                return value;
            }

            value = 99; // Modify after closure creation
            return show;
        }

        const showValue = outer();
        results.push('  let value = 10; show() returns value; value = 99;');
        results.push('  showValue() → ' + showValue() + ' (sees updated value!)');
        results.push('');

        // 7. Practical: Once function
        results.push('📌 Practical: Once Function:\n');

        function once(fn) {
            let called = false;
            let result;

            return function(...args) {
                if (!called) {
                    called = true;
                    result = fn(...args);
                }
                return result;
            };
        }

        const initialize = once(() => "Initialized!");
        results.push('  const initialize = once(() => "Initialized!");');
        results.push('  initialize() → ' + initialize());
        results.push('  initialize() → ' + initialize() + ' (only runs once)');
        results.push('');

        // 8. Practical: Memoization
        results.push('📌 Practical: Memoization:\n');

        function memoize(fn) {
            const cache = {};

            return function(n) {
                if (n in cache) {
                    return cache[n] + ' (cached)';
                }
                const result = fn(n);
                cache[n] = result;
                return result + ' (computed)';
            };
        }

        const slowSquare = memoize(n => n * n);
        results.push('  square(5) → ' + slowSquare(5));
        results.push('  square(5) → ' + slowSquare(5));
        results.push('  square(6) → ' + slowSquare(6));
        results.push('  square(6) → ' + slowSquare(6));

        document.getElementById('output').textContent = results.join('\n');

        // ============================================
        // Interactive: Counter
        // ============================================

        let interactiveCounter = createCounter();
        const counterDisplay = document.getElementById('counterDisplay');

        function incrementCounter() {
            const value = interactiveCounter();
            counterDisplay.textContent = value;
            counterDisplay.style.transform = 'scale(1.2)';
            setTimeout(() => {
                counterDisplay.style.transform = 'scale(1)';
            }, 200);
        }

        function resetCounter() {
            interactiveCounter = createCounter(); // New closure, fresh count
            counterDisplay.textContent = '0';
        }

        // ============================================
        // Interactive: Person
        // ============================================

        const interactivePerson = createPerson('Alice');
        document.getElementById('personName').textContent = interactivePerson.getName();
        document.getElementById('personAge').textContent = interactivePerson.getAge();

        function changeAge(amount) {
            const newAge = interactivePerson.getAge() + amount;
            interactivePerson.setAge(newAge);
            document.getElementById('personAge').textContent = interactivePerson.getAge();
        }

        function tryInvalidAge() {
            const oldAge = interactivePerson.getAge();
            interactivePerson.setAge(-5);
            const newAge = interactivePerson.getAge();

            if (oldAge === newAge) {
                alert('❌ Invalid age rejected! Age stays at ' + newAge);
            }
        }
    </script>

</body>
</html>

Quick Reference

What is a Closure?

AspectDescription
DefinitionA function that retains access to its lexical scope
Created whenAn inner function is defined inside an outer function
RetainsAccess to outer function’s variables
PersistsEven after the outer function has finished executing

Key Concepts

ConceptDescription
Lexical ScopeVariables scoped by location in code
Execution ContextEnvironment where code executes
Closure CreationInner function captures outer scope

Common Misconception

MisconceptionTruth
“Closures capture values at creation time”❌ Closures capture variable bindings (by reference)
“Changes to the variable don’t affect the closure”❌ Changes do affect the closure

Use Cases

Use CaseDescription
Data HidingPrivate variables
Factory FunctionsGenerate objects with behavior
Partial ApplicationCreate specialized functions
Event HandlersRemember per-handler data
Async StateRetain variables across async calls
MemoizationCache function results
Once FunctionsRun only once

Best Practices

Do This:

// Use closures for private state
function createCounter() {
    let count = 0;
    return {
        increment: () => ++count,
        getCount: () => count
    };
}

// Use closures for factory functions
function createMultiplier(factor) {
    return (num) => num * factor;
}

// Use closures for memoization
function memoize(fn) {
    const cache = new Map();
    return (n) => {
        if (cache.has(n)) return cache.get(n);
        const result = fn(n);
        cache.set(n, result);
        return result;
    };
}

// Use let in loops to create per-iteration closures
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 0, 1, 2 ✅
}

Don’t Do This:

// Don't use var in loops with closures
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100); // 3, 3, 3 ❌
}

// Don't create closures in loops unnecessarily
for (let i = 0; i < 1000000; i++) {
    functions.push(() => i); // Each closure holds memory
}

// Don't assume closures capture values
function outer() {
    let x = 10;
    return () => x;
}
const fn = outer();
// x is still accessible — but it's a reference, not a copy

// Don't forget closures hold memory
// (They can cause memory leaks if not cleaned up)

Common Pitfalls

PitfallProblemSolution
var in loopsAll closures see final valueUse let
Memory leaksClosures hold referencesNull out references when done
Expecting value captureClosures capture referencesBe aware of reference semantics
PerformanceMany closures = more memoryUse only when needed

Closure Memory Model

┌──────────────────────────────────────┐
│  Global Scope                        │
│                                      │
│  ┌────────────────────────────────┐  │
│  │  createCounter() scope         │  │
│  │  ┌──────────────────────────┐  │  │
│  │  │  let count = 0;          │  │  │
│  │  │  (retained by closure)   │  │  │
│  │  └──────────────────────────┘  │  │
│  │                                │  │
│  │  ┌──────────────────────────┐  │  │
│  │  │  return function() {     │  │  │
│  │  │    count++;              │  │  │
│  │  │  }                       │  │  │
│  │  └──────────────────────────┘  │  │
│  └────────────────────────────────┘  │
│                                      │
│  const counter = ← closure reference │
└──────────────────────────────────────┘

Pro Tip: Closures are one of JavaScript’s most powerful features. They enable encapsulation (private variables), factory functions, partial application, and state management in async operations. Remember: closures capture variable bindings (references), not values — so changes to the variable after creation do affect the closure. Always use let (not var) in loops with closures to get per-iteration bindings. And be mindful of memory — closures hold references to their outer scope, which can prevent garbage collection if not managed carefully!


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!