Skip to main content

Closures in JavaScript Explained

Answer​

A closure is a function that has access to variables from its outer (enclosing) scope, even after that outer function has returned. It "closes over" the variables it needs.

How Closures Work​

Basic Example​

function createCounter() {
let count = 0; // Private variable

return function () {
count++; // Accesses outer scope's count
return count;
};
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// Each call to createCounter creates a NEW closure
const counter2 = createCounter();
console.log(counter2()); // 1 (independent from counter)

Closure Memory Model​

function outer() {
const message = "Hello";

function inner() {
console.log(message);
}

return inner;
}

const fn = outer();
// outer() has returned, but 'message' is still in memory
// because 'inner' (now 'fn') still references it
fn(); // "Hello"

Practical Use Cases​

1. Data Privacy / Encapsulation​

function createBankAccount(initialBalance) {
let balance = initialBalance; // Private!

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

const account = createBankAccount(100);
account.deposit(50); // 150
account.withdraw(30); // 120
console.log(account.balance); // undefined (private!)
console.log(account.getBalance()); // 120

2. Function Factories​

function multiply(factor) {
return function (number) {
return number * factor;
};
}

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

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

3. Event Handlers​

function setupButton(buttonId, message) {
const button = document.getElementById(buttonId);

button.addEventListener("click", function () {
alert(message); // Closure captures 'message'
});
}

setupButton("btn1", "Hello from button 1!");
setupButton("btn2", "Hello from button 2!");

4. Memoization​

function memoize(fn) {
const cache = {}; // Closure captures this

return function (...args) {
const key = JSON.stringify(args);

if (key in cache) {
console.log("From cache");
return cache[key];
}

const result = fn(...args);
cache[key] = result;
return result;
};
}

const expensiveAdd = memoize((a, b) => {
console.log("Computing...");
return a + b;
});

expensiveAdd(1, 2); // Computing... → 3
expensiveAdd(1, 2); // From cache → 3

Common Closure Pitfall​

// The classic loop problem
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // Prints: 3, 3, 3
}, 1000);
}

// Why? All callbacks share the SAME 'i' variable
// By the time they run, i is 3

// Solution 1: Use let (creates new binding each iteration)
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // Prints: 0, 1, 2
}, 1000);
}

// Solution 2: IIFE (creates new scope each iteration)
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () {
console.log(j); // Prints: 0, 1, 2
}, 1000);
})(i);
}

Closures and Memory​

// Closures can cause memory to be retained
function createLeak() {
const largeData = new Array(1000000).fill("x");

return function () {
// Even if we don't use largeData, it's retained!
return "hello";
};
}

const fn = createLeak(); // largeData stays in memory

// To fix: Set reference to null when done
fn = null; // Now largeData can be garbage collected

Module Pattern (Classic)​

const Calculator = (function () {
// Private state
let result = 0;

// Private function
function validate(n) {
return typeof n === "number";
}

// Public interface
return {
add(n) {
if (validate(n)) result += n;
return this;
},
subtract(n) {
if (validate(n)) result -= n;
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
},
};
})();

Calculator.add(5).add(3).subtract(2);
console.log(Calculator.getResult()); // 6

Key Points​

  • Closure = function + its lexical environment
  • Functions remember where they were created
  • Enables data privacy and encapsulation
  • Used in callbacks, event handlers, memoization
  • Be careful with closures in loops (use let)
  • Watch for unintended memory retention