Scope & Closures
Practical Closure Patterns
Master practical JavaScript closure patterns. Implement the module pattern, memoization caches, and private state handlers.
1. Introduction
Closures are more than just a theoretical concept. They are a practical tool used to implement several design patterns in JavaScript, such as the Module Pattern, Memoization Caches, and Private State.
2. Why It Matters
Using closure design patterns lets you hide implementation details, cache expensive calculations, and write self-contained modules, helping you build clean and performant applications.
3. Real-World Analogy
Think of a Smart Office Desk:
- Module Pattern (Locked Filing Cabinet): A cabinet with a locked drawer for files (private state) and a top tray for forms (public API). You interact with the tray, while the cabinet hides the files.
- Memoization (Frequently Asked Questions Sheet): A sheet where the receptionist writes down answers to common questions. If a question is asked again, the receptionist reads the answer from the sheet instead of calling a technician to recalculate it.
4. Core Closure Patterns
Let's explore the three primary patterns:
1. The Module Pattern:
Uses closures to create public APIs while hiding internal implementation details, simulating classes with private properties.
2. Memoization (Caching):
Wraps a function with a cache object inside a closure. If the function is called with the same arguments again, the cached result is returned instead of re-running the calculation.
5. Practical Example
Here is a complete, working implementation of a memoized Fibonacci calculator:
6. Common Mistakes
- Unbounded cache size: In memoization, the cache grows indefinitely as the function is called with new arguments. This can lead to memory exhaustion in production. Implement a size limit or clean up strategies (like LRU caching).
7. Quick Quiz
Q1: Which closure pattern is used to simulate private methods and properties in JavaScript?
A) Iterator Pattern
B) Module Pattern
Answer: B — The Module Pattern uses IIFEs and closures to provide private scopes with public API access.
8. Scenario-Based Challenge
The LRU Memoizer:
Modify the standard memoize pattern to enforce a maximum cache size of 100 entries. If the limit is exceeded, delete the oldest cached item before adding the new result. Outline the storage design.
9. Debugging Exercise
Identify why this memoizer fails when arguments are objects:
function badMemoize(fn) {
const cache = new Map();
return function(arg) {
if (cache.has(arg)) {
return cache.get(arg);
}
const result = fn(arg);
cache.set(arg, result);
return result;
};
}
const check = badMemoize(x => x.id);
check({ id: 1 }); // runs calculation
check({ id: 1 }); // runs calculation again! Why?
View Solution
Diagnosis: A Map key comparison uses SameValueZero comparison rules. When objects are passed as keys, identical objects are treated as different keys because their memory references are different.
Fix: Stringify the arguments to construct a unique, value-based string key:
function goodMemoize(fn) {
const cache = new Map();
return function(arg) {
const key = JSON.stringify(arg);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(arg);
cache.set(key, result);
return result;
};
}
10. Interview Questions
🟢 Q1: Explain how closures enable the Module Pattern in JavaScript.
Answer: The Module Pattern uses an IIFE to define local variables and helper functions. The IIFE returns an object containing methods that reference those local variables. Because these methods are declared inside the IIFE scope, they form a closure over it, maintaining access to the private variables even after the IIFE has finished executing. The returned object acts as the public API, while the internal variables remain hidden.
11. Production Considerations
- • Cache Eviction: Always implement cache eviction policies (like Least Recently Used - LRU) for memoization caches in long-running applications (like Node.js servers) to prevent memory leaks.