What are Closures
What are Closures
A closure is a function that remembers the variables from its outer scope, even after the outer function has finished executing.
Basic Example
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
// count is still accessible!
How It Works
function createMultiplier(multiplier) {
// multiplier is 'closed over'
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
double(5); // 10
triple(5); // 15
// multiplier is remembered!
Scope Chain
function outer() {
let a = 1;
function middle() {
let b = 2;
function inner() {
let c = 3;
console.log(a, b, c); // 1, 2, 3
}
return inner;
}
return middle;
}
const fn = outer()();
fn(); // 1, 2, 3
Lexical Environment
function greet(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = greet('Hello');
const sayHi = greet('Hi');
sayHello('John'); // "Hello, John!"
sayHi('Jane'); // "Hi, Jane!"
Closures vs Global Variables
// Bad: global variable
let count = 0;
function increment() {
count++;
}
// Good: closure
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
Closure Use Cases
Closure Use Cases
Data Privacy / Encapsulation
function createBankAccount(initialBalance) {
let balance = initialBalance;
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(1000);
account.deposit(500); // 1500
account.withdraw(200); // 1300
account.getBalance(); // 1300
// balance is private!
Function Factories
function createAdder(base) {
return function(value) {
return base + value;
};
}
const add10 = createAdder(10);
const add100 = createAdder(100);
add10(5); // 15
add100(5); // 105
Memoization
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = fn(...args);
}
return cache[key];
};
}
const expensiveCalculation = memoize((n) => {
console.log('Computing...');
return n * n;
});
expensiveCalculation(5); // Computing... 25
expensiveCalculation(5); // 25 (cached)
Event Handlers
function setupButton(buttonId, message) {
const button = document.getElementById(buttonId);
button.addEventListener('click', function() {
alert(message); // message is closed over
});
}
setupButton('btn1', 'Hello!');
setupButton('btn2', 'Goodbye!');
Iterators
function createRangeIterator(start, end) {
let current = start;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { done: true };
}
};
}
const iter = createRangeIterator(1, 5);
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
Debounce/Throttle
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const debouncedSearch = debounce((query) => {
console.log('Searching:', query);
}, 300);
Memory Considerations
Memory Considerations
Closures and Memory
function createHeavyClosure() {
const largeArray = new Array(1000000).fill('data');
return function() {
// largeArray is kept in memory
return largeArray.length;
};
}
const fn = createHeavyClosure();
// largeArray stays in memory as long as fn exists
Memory Leaks
// Bad: unintended closure
function setup() {
const element = document.getElementById('app');
element.addEventListener('click', function() {
// 'element' is closed over, can't be garbage collected
console.log('clicked');
});
}
// Better: remove event listener
function setup() {
const element = document.getElementById('app');
function handleClick() {
console.log('clicked');
}
element.addEventListener('click', handleClick);
return function cleanup() {
element.removeEventListener('click', handleClick);
};
}
Breaking Closures
// Nullify references
function createClosure() {
let data = new Array(1000000);
return function() {
return data.length;
};
}
let fn = createClosure();
fn(); // Works
// Break the closure
fn = null; // data can now be garbage collected
WeakMap for Private Data
const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}
// When User instance is garbage collected,
// its private data is also collected
IIFE for Cleanup
function processData() {
const cache = new Map();
// Process and clean up
const result = (function() {
// Use cache
return 'result';
})();
// cache is still accessible
return result;
}
Best Practices
// 1. Don't close over unnecessary data
function bad() {
const hugeData = getHugeData();
return function() {
return hugeData.length; // Closes over hugeData
};
}
function good() {
const hugeData = getHugeData();
const length = hugeData.length;
return function() {
return length; // Only closes over length
};
}
// 2. Use WeakMap/WeakSet for object references
// 3. Clean up event listeners
// 4. Set references to null when done
Practice Problems
Create a reusable React component implementing Closures. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Closures using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Closures for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What is a closure?
2. When does a closure keep variables in memory?
3. What is a common use case for closures?
4. How do you break a closure?
5. What problem can closures cause?
Flashcards
Question
What is a closure?
Click to reveal answer
Answer
A function that remembers and can access variables from its outer scope, even after the outer function has returned.
Question
When do closed-over variables get garbage collected?
Click to reveal answer
Answer
When the closure function no longer exists and no references to it remain.
Question
What is a common use case for closures?
Click to reveal answer
Answer
Data privacy, memoization, function factories, and event handlers.
Question
How can closures cause memory leaks?
Click to reveal answer
Answer
By keeping references to large objects that are no longer needed.
Question
What is a function factory?
Click to reveal answer
Answer
A function that returns new functions with preset parameters, using closures to remember those parameters.
Revision Notes
Key Takeaways
- 1.Closures remember variables from their outer scope
- 2.They enable data privacy and encapsulation
- 3.Variables stay in memory while the closure exists
- 4.Common uses: counters, factories, memoization
- 5.Be mindful of memory with closures
Interview Tips
- •Explain closures with a code example
- •Know the difference between closures and global variables
- •Understand memory implications of closures
- •Be able to create private variables with closures
Cheat Sheet
Closures Cheat Sheet
What is a Closure?
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
counter(); // 1
counter(); // 2
Use Cases
- Data Privacy
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
- Function Factories
function createMultiplier(n) {
return (x) => x * n;
}
- Memoization
function memoize(fn) {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
return cache[key] || (cache[key] = fn(...args));
};
}
Memory
- Closures keep variables in memory
- Set references to null to break closures
- Use WeakMap for object references