JavaScript Closures: Complete Guide for Developers
Master JavaScript closures: lexical scope, scope chain, memory model, data privacy, module patterns, practical examples, and interview questions with detailed explanations.
JavaScript Closures: Complete Guide for Developers
📋 Table of Contents
- What is a Closure?
- Why Do We Need Closures?
- Real-World Use Cases
- Syntax
- Internal Working
- Memory Diagram
- Data Flow Diagram
- Code Examples
- Common Mistakes
- Performance Considerations
- Interview Questions
- Cheat Sheet
- Summary
1. What is a Closure?
A closure is a function bundled together with references to its surrounding state (lexical environment).
Simple Definition
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE = FUNCTION + LEXICAL ENVIRONMENT │
└─────────────────────────────────────────────────────────────┘
Closure Components:
───────────────────
1. Inner Function
2. References to outer variables
3. Preserved lexical environment
Key Characteristic:
──────────────────
A function that "remembers" variables from its outer scope,
even after the outer function has finished executing.
Visual Representation
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE STRUCTURE │
└─────────────────────────────────────────────────────────────┘
Outer Function
│
├─── Variable (captured)
│
└─── Inner Function
│
└─── References outer variable
(creates closure)
When inner function is returned:
────────────────────────────────
┌──────────────────────────────┐
│ Inner Function │
│ ├─ Function code │
│ └─ [[Scope]] (hidden) │
│ └─ Reference to outer │
│ variable │
└──────────────────────────────┘
Closure Characteristics
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE PROPERTIES │
└─────────────────────────────────────────────────────────────┘
✓ Preserves outer function's variables
✓ Creates private variables
✓ Maintains state between function calls
✓ Enables data encapsulation
✓ Forms the basis of module pattern
✓ Powers React hooks and callbacks
✓ Essential for async programming
2. Why Do We Need Closures?
Closures solve critical programming challenges.
Problem Without Closures
// Without closures - no persistent state
function createCounter() {
let count = 0;
count++;
return count;
}
console.log(createCounter()); // 1
console.log(createCounter()); // 1 (resets each time)
console.log(createCounter()); // 1 (no persistence)
Solution With Closures
// With closures - persistent state
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3 (state persists!)
Benefits
- Data Privacy: Hide implementation details
- State Persistence: Maintain state between calls
- Encapsulation: Bundle data with functions
- Factory Functions: Create multiple instances with private state
- Callbacks: Preserve context in async operations
- Module Pattern: Create private and public APIs
- Memoization: Cache function results
- Currying: Create specialized functions
3. Real-World Use Cases
1. Private Variables (Data Encapsulation)
function createBankAccount(initialBalance) {
// Private variable (closure)
let balance = initialBalance;
// Public API
return {
deposit(amount) {
if (amount > 0) {
balance += amount;
return `Deposited: $${amount}. New balance: $${balance}`;
}
return "Invalid amount";
},
withdraw(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return `Withdrawn: $${amount}. New balance: $${balance}`;
}
return "Insufficient funds or invalid amount";
},
getBalance() {
return `Current balance: $${balance}`;
}
};
}
const account = createBankAccount(1000);
console.log(account.deposit(500)); // "Deposited: $500. New balance: $1500"
console.log(account.withdraw(200)); // "Withdrawn: $200. New balance: $1300"
console.log(account.getBalance()); // "Current balance: $1300"
// balance is private - cannot access directly
console.log(account.balance); // undefined
2. Event Handlers
function setupButtons() {
const buttons = document.querySelectorAll('.btn');
buttons.forEach((button, index) => {
// Each button gets its own closure
let clickCount = 0;
button.addEventListener('click', () => {
clickCount++;
console.log(`Button ${index} clicked ${clickCount} times`);
});
});
}
// Each button maintains its own click count through closure
3. Module Pattern
const ShoppingCart = (function() {
// Private variables
let items = [];
let total = 0;
// Private functions
function calculateTotal() {
total = items.reduce((sum, item) => sum + item.price, 0);
}
function validateItem(item) {
return item &&
typeof item.name === 'string' &&
typeof item.price === 'number' &&
item.price > 0;
}
// Public API
return {
addItem(item) {
if (!validateItem(item)) {
throw new Error("Invalid item");
}
items.push(item);
calculateTotal();
return this;
},
removeItem(itemName) {
items = items.filter(item => item.name !== itemName);
calculateTotal();
return this;
},
getItems() {
return [...items]; // Return copy
},
getTotal() {
return total;
},
clear() {
items = [];
total = 0;
return this;
}
};
})();
// Usage
ShoppingCart
.addItem({ name: "Book", price: 20 })
.addItem({ name: "Pen", price: 5 });
console.log(ShoppingCart.getTotal()); // 25
4. Memoization (Caching)
function memoize(fn) {
const cache = {}; // Closure variable
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log('Returning cached result');
return cache[key];
}
console.log('Computing result');
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Expensive function
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // Computing result: 55
console.log(memoizedFib(10)); // Returning cached result: 55
5. Currying
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
const multiplyBy2 = multiply(2);
const multiplyBy2And3 = multiplyBy2(3);
console.log(multiplyBy2And3(4)); // 24
// Or chain directly
console.log(multiply(2)(3)(4)); // 24
4. Syntax
Basic Closure
// ═══════════════════════════════════════════════════════════
// BASIC CLOSURE PATTERN
// ═══════════════════════════════════════════════════════════
function outer() {
const outerVar = "I'm from outer";
function inner() {
console.log(outerVar); // Accesses outer variable
}
return inner;
}
const closureFunc = outer();
closureFunc(); // "I'm from outer"
Closure with Parameters
// ═══════════════════════════════════════════════════════════
// CLOSURE WITH PARAMETERS
// ═══════════════════════════════════════════════════════════
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Closure Returning Object
// ═══════════════════════════════════════════════════════════
// CLOSURE RETURNING OBJECT (MODULE PATTERN)
// ═══════════════════════════════════════════════════════════
function createPerson(name) {
let age = 0; // Private variable
return {
getName() {
return name;
},
getAge() {
return age;
},
haveBirthday() {
age++;
return `Happy birthday! ${name} is now ${age}`;
}
};
}
const person = createPerson("John");
console.log(person.getName()); // "John"
console.log(person.haveBirthday()); // "Happy birthday! John is now 1"
console.log(person.getAge()); // 1
IIFE Closure
// ═══════════════════════════════════════════════════════════
// IMMEDIATELY INVOKED FUNCTION EXPRESSION (IIFE) CLOSURE
// ═══════════════════════════════════════════════════════════
const counter = (function() {
let count = 0;
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
}
};
})();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
Arrow Function Closure
// ═══════════════════════════════════════════════════════════
// ARROW FUNCTION CLOSURE
// ═══════════════════════════════════════════════════════════
const createGreeter = (greeting) => {
return (name) => {
return `${greeting}, ${name}!`;
};
};
const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");
console.log(sayHello("John")); // "Hello, John!"
console.log(sayHi("Jane")); // "Hi, Jane!"
5. Internal Working
Closure Creation Process
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE CREATION MECHANISM │
└─────────────────────────────────────────────────────────────┘
Step 1: Outer Function Called
──────────────────────────────
function outer() {
const x = 10;
return function inner() {
console.log(x);
};
}
Step 2: Execution Context Created
──────────────────────────────────
┌──────────────────────────────┐
│ outer() Execution Context │
│ ├─ x: 10 │
│ └─ inner: [Function] │
└──────────────────────────────┘
Step 3: Inner Function Created
───────────────────────────────
┌──────────────────────────────┐
│ inner() Function Object │
│ ├─ Code: console.log(x) │
│ └─ [[Scope]]: Reference to │
│ outer's environment │
└──────────────────────────────┘
Step 4: Inner Function Returned
────────────────────────────────
outer() execution context is popped,
BUT x is preserved because inner()
still references it!
Step 5: Closure Formed
───────────────────────
┌──────────────────────────────┐
│ Closure │
│ ├─ inner() function │
│ └─ Preserved environment: │
│ └─ x: 10 │
└──────────────────────────────┘
Lexical Environment Chain
┌─────────────────────────────────────────────────────────────┐
│ LEXICAL ENVIRONMENT CHAIN │
└─────────────────────────────────────────────────────────────┘
Code:
─────
const global = "Global";
function outer() {
const outerVar = "Outer";
function middle() {
const middleVar = "Middle";
function inner() {
const innerVar = "Inner";
console.log(global, outerVar, middleVar, innerVar);
}
return inner;
}
return middle;
}
Lexical Environment Chain:
──────────────────────────
inner() [[Scope]]
↓
middle() Lexical Environment
├─ middleVar: "Middle"
└─ [[Outer]]: ↓
outer() Lexical Environment
├─ outerVar: "Outer"
└─ [[Outer]]: ↓
Global Lexical Environment
└─ global: "Global"
When inner() executes:
──────────────────────
1. Look for innerVar → Found locally
2. Look for middleVar → Found in middle()
3. Look for outerVar → Found in outer()
4. Look for global → Found in global scope
6. Memory Diagram
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE MEMORY LAYOUT │
└─────────────────────────────────────────────────────────────┘
Code:
─────
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
Memory Structure:
─────────────────
Global Memory:
┌─────────────────────────────────────┐
│ createCounter: [Function] │
│ counter1: [Function] │
│ counter2: [Function] │
└─────────────────────────────────────┘
Closure 1 (counter1):
┌─────────────────────────────────────┐
│ Function Object │
│ ├─ Code: count++; return count; │
│ └─ [[Scope]]: │
│ └─ count: 0 → 1 → 2 → ... │
└─────────────────────────────────────┘
Closure 2 (counter2):
┌─────────────────────────────────────┐
│ Function Object │
│ ├─ Code: count++; return count; │
│ └─ [[Scope]]: │
│ └─ count: 0 → 1 → 2 → ... │
└─────────────────────────────────────┘
Each closure has its OWN copy of 'count'!
After counter1() called 3 times:
────────────────────────────────
counter1's count: 3
counter2's count: 0 (independent)
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE MEMORY LIFECYCLE │
└─────────────────────────────────────────────────────────────┘
1. Function Created:
┌──────────────────┐
│ Closure Memory │
│ Allocated │
└──────────────────┘
2. Function Executed:
┌──────────────────┐
│ Variables │
│ Updated │
└──────────────────┘
3. Function Reference Exists:
┌──────────────────┐
│ Memory │
│ Preserved │
└──────────────────┘
4. No More References:
┌──────────────────┐
│ Garbage │
│ Collected │
└──────────────────┘
7. Data Flow Diagram
┌─────────────────────────────────────────────────────────────┐
│ CLOSURE EXECUTION FLOW │
└─────────────────────────────────────────────────────────────┘
Outer Function Called
↓
┌──────────────────┐
│ Create Execution │
│ Context │
└──────────────────┘
↓
┌──────────────────┐
│ Initialize │
│ Variables │
└──────────────────┘
↓
┌──────────────────┐
│ Create Inner │
│ Function │
└──────────────────┘
↓
┌──────────────────┐
│ Link Inner to │
│ Outer Scope │
│ ([[Scope]]) │
└──────────────────┘
↓
┌──────────────────┐
│ Return Inner │
│ Function │
└──────────────────┘
↓
┌──────────────────┐
│ Outer Context │
│ Popped │
│ (but preserved) │
└──────────────────┘
↓
Inner Function Called
↓
┌──────────────────┐
│ Create New │
│ Execution Context│
└──────────────────┘
↓
┌──────────────────┐
│ Access Variables │
│ via [[Scope]] │
└──────────────────┘
↓
┌──────────────────┐
│ Execute Code │
└──────────────────┘
↓
┌──────────────────┐
│ Return Result │
└──────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ VARIABLE ACCESS FLOW IN CLOSURE │
└─────────────────────────────────────────────────────────────┘
Variable Reference
↓
┌──────────────────┐
│ Check Local │
│ Scope │
└──────────────────┘
↓
┌────┴────┐
│ Found? │
└────┬────┘
Yes │ No
↓ ↓
Return ┌──────────────────┐
Value │ Check [[Scope]] │
│ (Closure) │
└──────────────────┘
↓
┌────┴────┐
│ Found? │
└────┬────┘
Yes │ No
↓ ↓
Return Continue
Value Up Chain
8. Code Examples
Example 1: Counter with Multiple Operations
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
add(value) {
count += value;
return count;
},
reset() {
count = initialValue;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.add(5)); // 17
console.log(counter.decrement()); // 16
console.log(counter.reset()); // 10
console.log(counter.getCount()); // 10
Example 2: Function Factory
function createFormatter(prefix, suffix) {
return function(text) {
return `${prefix}${text}${suffix}`;
};
}
const addBrackets = createFormatter('[', ']');
const addParens = createFormatter('(', ')');
const addQuotes = createFormatter('"', '"');
console.log(addBrackets('Hello')); // "[Hello]"
console.log(addParens('World')); // "(World)"
console.log(addQuotes('JavaScript')); // '"JavaScript"'
Example 3: Private Methods
function createUser(name, email) {
// Private variables
let _name = name;
let _email = email;
let _loginAttempts = 0;
// Private method
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// Public API
return {
getName() {
return _name;
},
setName(newName) {
if (newName && newName.length > 0) {
_name = newName;
return true;
}
return false;
},
getEmail() {
return _email;
},
setEmail(newEmail) {
if (validateEmail(newEmail)) {
_email = newEmail;
return true;
}
return false;
},
login(password) {
_loginAttempts++;
// Login logic here
return `Login attempt ${_loginAttempts}`;
},
getLoginAttempts() {
return _loginAttempts;
}
};
}
const user = createUser("John", "[email protected]");
console.log(user.getName()); // "John"
console.log(user.setEmail("invalid")); // false
console.log(user.setEmail("[email protected]")); // true
console.log(user.getEmail()); // "[email protected]"
// Private variables are not accessible
console.log(user._name); // undefined
console.log(user._email); // undefined
Example 4: Closure in Loops (Classic Problem)
// ❌ Problem: var is function-scoped
console.log("Problem with var:");
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Output: 4, 4, 4 (all callbacks share same 'i')
// ✅ Solution 1: Use let (block-scoped)
console.log("Solution with let:");
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Output: 1, 2, 3 (each iteration has own 'i')
// ✅ Solution 2: IIFE creates closure
console.log("Solution with IIFE:");
for (var i = 1; i <= 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index);
}, 100);
})(i);
}
// Output: 1, 2, 3 (IIFE captures 'i' as 'index')
// ✅ Solution 3: Pass parameter to setTimeout
console.log("Solution with setTimeout parameter:");
for (var i = 1; i <= 3; i++) {
setTimeout(function(index) {
console.log(index);
}, 100, i);
}
// Output: 1, 2, 3
Example 5: Memoization with Closure
function createMemoizedFunction(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`Cache hit for ${key}`);
return cache.get(key);
}
console.log(`Computing for ${key}`);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Expensive calculation
function expensiveOperation(n) {
let sum = 0;
for (let i = 0; i < n * 1000000; i++) {
sum += i;
}
return sum;
}
const memoized = createMemoizedFunction(expensiveOperation);
console.log(memoized(5)); // Computing for [5]
console.log(memoized(5)); // Cache hit for [5] (instant!)
console.log(memoized(10)); // Computing for [10]
console.log(memoized(5)); // Cache hit for [5] (instant!)
9. Common Mistakes
Mistake 1: Expecting Variables to Reset
// ❌ Wrong: Expecting count to reset
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2 (not 1!)
console.log(counter()); // 3 (count persists)
// ✅ Correct: Understanding closure behavior
// If you want to reset, provide a reset method
function createCounter() {
let count = 0;
return {
increment() {
return ++count;
},
reset() {
count = 0;
}
};
}
Mistake 2: Memory Leaks with Large Objects
// ❌ Wrong: Keeping large objects in closure unnecessarily
function createHandler() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log('Handler called');
// largeData is kept in memory even though not used!
};
}
// ✅ Correct: Don't capture unnecessary variables
function createHandler() {
const largeData = new Array(1000000).fill('data');
processData(largeData); // Use it
// largeData will be garbage collected
return function() {
console.log('Handler called');
// No reference to largeData
};
}
Mistake 3: Shared Closure in Loops
// ❌ Wrong: All functions share same variable
function createFunctions() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions.push(function() {
return 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!)
// ✅ Correct: Use let or create separate closure
function createFunctions() {
const functions = [];
for (let i = 0; i < 3; i++) {
functions.push(function() {
return i;
});
}
return functions;
}
const fns = createFunctions();
console.log(fns[0]()); // 0
console.log(fns[1]()); // 1
console.log(fns[2]()); // 2
Mistake 4: Modifying Closure Variables Unexpectedly
// ❌ Wrong: Unexpected side effects
function createSharedCounter() {
let count = 0;
return {
counter1: () => ++count,
counter2: () => ++count
};
}
const counters = createSharedCounter();
console.log(counters.counter1()); // 1
console.log(counters.counter2()); // 2 (shares same count!)
// ✅ Correct: Separate closures if needed
function createIndependentCounters() {
return {
counter1: (() => {
let count = 0;
return () => ++count;
})(),
counter2: (() => {
let count = 0;
return () => ++count;
})()
};
}
const counters = createIndependentCounters();
console.log(counters.counter1()); // 1
console.log(counters.counter2()); // 1 (independent!)
Mistake 5: Not Understanding 'this' in Closures
// ❌ Wrong: 'this' not captured by closure
const obj = {
name: "Object",
createMethod() {
return function() {
console.log(this.name); // 'this' is not captured
};
}
};
const method = obj.createMethod();
method(); // undefined (or error in strict mode)
// ✅ Correct: Use arrow function or bind
const obj = {
name: "Object",
createMethod() {
return () => {
console.log(this.name); // Arrow function captures 'this'
};
}
};
const method = obj.createMethod();
method(); // "Object"
10. Performance Considerations
1. Memory Usage
// ❌ Inefficient: Creates new closure for each instance
class Component {
constructor() {
this.handleClick = () => {
// New closure created for each instance
console.log('Clicked');
};
}
}
// ✅ Efficient: Share method across instances
class Component {
handleClick() {
console.log('Clicked');
}
}
2. Closure Scope Chain
// ❌ Slow: Deep closure chain
function level1() {
const a = 1;
return function level2() {
const b = 2;
return function level3() {
const c = 3;
return function level4() {
// Accessing 'a' requires traversing 3 levels
return a + b + c;
};
};
};
}
// ✅ Fast: Flatten or cache values
function level1() {
const a = 1;
return function level2() {
const b = 2;
const cachedA = a; // Cache outer value
return function level3() {
const c = 3;
return function level4() {
return cachedA + b + c; // Faster access
};
};
};
}
3. Avoid Unnecessary Closures
// ❌ Inefficient: Closure not needed
function processArray(arr) {
return arr.map(function(item) {
return item * 2;
});
}
// ✅ Efficient: Use pure function
function double(item) {
return item * 2;
}
function processArray(arr) {
return arr.map(double); // No closure overhead
}
11. Interview Questions
Q1: What is a closure in JavaScript?
Answer: A closure is a function bundled together with references to its surrounding state (lexical environment). It allows a function to access variables from an outer function even after the outer function has finished executing.
function outer() {
const message = "Hello";
return function inner() {
console.log(message); // Accesses outer variable
};
}
const fn = outer();
fn(); // "Hello" (closure preserves 'message')
Q2: How do closures work internally?
Answer: When a function is created, it maintains a reference to its lexical environment through a hidden [[Scope]] property. This reference keeps outer variables alive even after the outer function returns.
function createCounter() {
let count = 0; // Stored in closure
return function() {
count++;
return count;
};
}
// The returned function's [[Scope]] references 'count'
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Q3: What are practical uses of closures?
Answer:
- Data Privacy: Creating private variables
- State Persistence: Maintaining state between function calls
- Module Pattern: Encapsulating code
- Callbacks: Preserving context in async operations
- Memoization: Caching function results
- Currying: Creating specialized functions
// Data Privacy
function createSecret(secret) {
return {
getSecret() {
return secret;
}
};
}
const mySecret = createSecret("password123");
console.log(mySecret.getSecret()); // "password123"
console.log(mySecret.secret); // undefined (private!)
Q4: What's the classic closure problem in loops?
Answer: When using var in loops with async callbacks, all callbacks share the same variable due to function scoping.
// Problem
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3
// Solution 1: Use let (block scope)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
// Solution 2: IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// Output: 0, 1, 2
Q5: Can closures cause memory leaks?
Answer: Yes, if closures unnecessarily retain references to large objects or DOM elements that are no longer needed.
// Memory leak
function createLeak() {
const largeArray = new Array(1000000);
return function() {
console.log('Function called');
// largeArray is kept in memory unnecessarily
};
}
// Fix: Don't capture unnecessary variables
function noLeak() {
const largeArray = new Array(1000000);
processArray(largeArray);
return function() {
console.log('Function called');
// largeArray can be garbage collected
};
}
Q6: How do closures relate to the module pattern?
Answer: The module pattern uses closures to create private variables and methods, exposing only a public API.
const Module = (function() {
// Private
let privateVar = 0;
function privateMethod() {
return privateVar;
}
// Public API
return {
publicMethod() {
privateVar++;
return privateMethod();
}
};
})();
console.log(Module.publicMethod()); // 1
console.log(Module.privateVar); // undefined
12. Cheat Sheet
// ═══════════════════════════════════════════════════════════
// CLOSURE QUICK REFERENCE
// ═══════════════════════════════════════════════════════════
// DEFINITION
// ──────────
Closure = Function + Lexical Environment
// BASIC PATTERN
// ─────────────
function outer() {
const variable = "value";
return function inner() {
return variable; // Closure
};
}
// COMMON PATTERNS
// ───────────────
// 1. Counter
function createCounter() {
let count = 0;
return () => ++count;
}
// 2. Private Variables
function createObject() {
let private = "secret";
return {
getPrivate: () => private
};
}
// 3. Module Pattern
const Module = (function() {
let private = 0;
return {
public() { return private; }
};
})();
// 4. Memoization
function memoize(fn) {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
return cache[key] || (cache[key] = fn(...args));
};
}
// 5. Currying
const curry = (a) => (b) => (c) => a + b + c;
// LOOP CLOSURE PROBLEM
// ────────────────────
// ❌ Wrong
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3
// ✅ Correct
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
// BENEFITS
// ────────
✓ Data privacy
✓ State persistence
✓ Encapsulation
✓ Factory functions
✓ Callbacks
✓ Module pattern
// RISKS
// ─────
✗ Memory usage
✗ Potential memory leaks
✗ Performance overhead
// BEST PRACTICES
// ──────────────
✓ Use for data privacy
✓ Avoid unnecessary closures
✓ Be aware of memory implications
✓ Use let in loops
✓ Don't capture large objects unnecessarily
✓ Understand 'this' behavior
13. Summary
Key Takeaways
✅ Closure = Function + Lexical Environment
✅ Preserves outer variables after function returns
✅ Enables data privacy and encapsulation
✅ Powers module pattern, callbacks, React hooks
✅ Creates persistent state between function calls
✅ Requires understanding of lexical scope
✅ Can cause memory issues if misused
✅ Essential for modern JavaScript patterns
✅ Loop problem solved with let or IIFE
✅ Most important JavaScript concept to master
Best Practices
- Use closures for data privacy and encapsulation
- Avoid capturing unnecessary large objects
- Use let in loops to avoid closure problems
- Understand memory implications
- Create factory functions for multiple instances
- Implement module pattern for code organization
- Be careful with 'this' in closures
- Cache frequently accessed outer variables
- Release references when no longer needed
- Test for memory leaks in production code
Closures are fundamental to JavaScript and enable powerful patterns like modules, callbacks, and functional programming techniques. Mastering closures is essential for writing professional JavaScript code.