What it does: When a function is created inside another function, it “closes over” (captures) variables from the outer scope. Even after the outer function finishes, the inner function remembers those variables.

What Actually Happens - The Behavior

Let’s say you have this:

javascript

function parent() {
    let a = 10;
    return function inner() {
        console.log(a);
    }
}
 
let closure = parent();
closure(); // prints 10

What you see: The inner function can still access a even though parent() finished running.

Reference vs Copy - The Key Point

function parent() {
    let a = 10;
    return function inner() {
        a++;
        console.log(a);
    }
}
 
let closure = parent();
closure(); // prints 11
closure(); // prints 12

Behavior: The variable a keeps changing! This proves it’s a reference, not a copy. cause say if it took a as literal copy with it and you called closure() , time it would invoke a=10 because its refence you are modifying same memory space this incrementing value

function counter() {
    let count = 0;
    
    return function() {
        count++; // Modifies the SAME count variable
        return count;
    };
}
 
const increment = counter();
console.log(increment()); // 1
console.log(increment()); // 2 (same count variable!)
console.log(increment()); // 3

What happens in memory:

  1. counter() creates count at memory address 0x100
  2. Inner function captures reference to 0x100 (not the value)
  3. counter() finishes, removed from call stack
  4. But count at 0x100 stays alive because inner function references it
  5. Each increment() call modifies the same memory location

Why Don’t Variables Get Destroyed?

You’re right about garbage collection! Here’s what happens:

  1. Normal case: When parent() finishes, its variables would be garbage collected
  2. Closure case: The returned function still has a reference to a
  3. Garbage collector logic: “Someone still needs a, so I won’t delete it”
  4. Result: Variable a stays alive in memory

The garbage collector uses “mark and sweep” - it only removes things with no references. Since the closure references a, it stays alive.

It’s Not Just About Function Definitions

The closure isn’t just the function definition - it’s the function + its captured environment. When you call closure(), it actually executes and uses those referenced variables.

All Forms of Closures

Closures happen whenever a function accesses variables from outside its own scope:

  1. Returned functions (your example)
  2. Callbacks: setTimeout(function() { console.log(a); }, 1000)
  3. Event handlers: button.onclick = function() { console.log(a); }
  4. Functions stored in variables or objects

You can’t return methods from objects in the same way because objects don’t create new scopes like functions do.

1. Returned Functions (Most Common)

function factory() {
    let x = 1;
    return function() { return x; };
}

2. Callback Functions

function setup() {
    let config = "settings";
    
    setTimeout(function() {
        console.log(config); // Closure over config
    }, 1000);
}

3. Event Handlers

function addHandler() {
    let clickCount = 0;
    
    button.onclick = function() {
        clickCount++; // Closure over clickCount
        console.log(clickCount);
    };
}

4. IIFE with Closures

const module = (function() {
    let private = "secret";
    
    return {
        getPrivate() {
            return private; // Closure over private
        }
    };
})();

5. Loop Variables (let creates closures)

javascript

for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i); // Each callback closes over different 'i'
    }, 100);
}

Interview Gotchas:

Gotcha 1: Shared vs Individual Closures

javascript

function createFunctions() {
    let functions = [];
    
    for (var i = 0; i < 3; i++) {
        functions.push(function() {
            return i; // All closures share same 'i'
        });
    }
    
    return functions;
}
 
const fns = createFunctions();
console.log(fns[0]()); // 3 (not 0!)
console.log(fns[1]()); // 3 (not 1!)
console.log(fns[2]()); // 3 (not 2!)

Gotcha 2: Closure in Object Methods

javascript

function createObject() {
    let secret = "hidden";
    
    return {
        getSecret() {
            return secret; // Method has closure over secret
        }
    };
}

Gotcha 3: Multiple Closures, Same Variable

javascript

function factory() {
    let shared = 0;
    
    return {
        increment() { shared++; },
        decrement() { shared--; },
        getValue() { return shared; }
    };
}
 
const obj = factory();
obj.increment(); // shared = 1
obj.increment(); // shared = 2
console.log(obj.getValue()); // 2

Gotcha 4: Closure Memory Leaks

javascript

function problematic() {
    let bigData = new Array(1000000).fill("data");
    
    return function() {
        console.log("hi"); // Doesn't use bigData but keeps it alive!
    };
}

Mental Model - The Snapshot:

Think of closure as taking a “live photo”:

  • When inner function is created, it takes a snapshot of what variables it needs
  • The snapshot contains addresses, not values
  • Even after outer function is gone, the snapshot keeps those memory addresses alive
  • Multiple closures can share the same addresses

Why Only Functions Create Closures:

Objects/Classes don’t create closures because:

  • They don’t have lexical scope
  • Properties are accessed via this, not scope chain
  • Methods are just functions (which can have closures)

javascript

const obj = {
    method() {
        // This is just a function, can have closures like any function
    }
};

Bottom line: Closures happen when inner functions capture references to outer scope variables. The captured variables stay alive in memory as long as the closure function is reachable. Think “live reference snapshot” - not copying values, but keeping memory addresses alive through garbage collection.