Advanced Closures
Powerful patterns built on closure mechanics.
Currying
Transform a function with multiple arguments into a chain of single-argument functions.
// Normal function:
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); // 6
// Curried version:
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
curriedAdd(1)(2)(3); // 6Currying breaks a multi-argument function into nested single-argument functions. Each closure remembers its argument.
1 / 3
Partial Application
Fix some arguments and return a function that takes the rest.
// Create specialized functions from general ones:
function log(level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
}
// Partial: fix the level
function createLogger(level) {
return (message) => {
log(level, new Date().toISOString(), message);
};
}
const error = createLogger("ERROR");
const info = createLogger("INFO");
error("Connection failed");
// [ERROR] 2024-01-15T...: Connection failed
info("Server started");
// [INFO] 2024-01-15T...: Server started// Using .bind() for partial application:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2); // fix a=2
const triple = multiply.bind(null, 3); // fix a=3
double(5); // 10
triple(5); // 15Module Pattern
Use closures to create private state with a public API.
// The Revealing Module Pattern:
const Counter = (() => {
// Private state (enclosed)
let count = 0;
const history = [];
// Private function
function log(action) {
history.push({ action, count, time: Date.now() });
}
// Public API (returned)
return {
increment() { count++; log("inc"); },
decrement() { count--; log("dec"); },
getCount() { return count; },
getHistory() { return [...history]; },
};
})();
Counter.increment();
Counter.getCount(); // 1
Counter.count; // undefined (private!)
Counter.history; // undefined (private!)// Module with private state:
0
count is private — only accessible through the API
Memoization
Cache expensive computation results using closures.
function memoize(fn) {
const cache = new Map(); // enclosed — persists!
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Cache hit!");
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Usage:
const expensiveCalc = memoize((n) => {
console.log("Computing...");
return n * n * n;
});
expensiveCalc(5); // "Computing..." → 125
expensiveCalc(5); // "Cache hit!" → 125 (instant)
expensiveCalc(3); // "Computing..." → 27// Memoized Fibonacci (exponential → linear):
const fib = memoize(function(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
});
fib(50); // instant! (without memo: would take forever)Closure & Scope
Advanced scope interactions and gotchas.
// Closures share the same scope object:
function makeActions() {
const actions = [];
for (var i = 0; i < 3; i++) {
actions.push(() => console.log(i));
}
return actions;
}
const acts = makeActions();
acts[0](); // 3 (not 0!)
acts[1](); // 3
acts[2](); // 3All three closures share the same 'i' variable (var is function-scoped). By the time they run, i = 3.
💡 Closures capture variables by reference, not value
1 / 3
FAQ
Common questions about advanced closure patterns.