Higher-Order Functions & Closures
Closure কোনো জাদু নয় — এটি সংরক্ষিত scope
1. Higher-Order Functions (HOF)
An HOF is a function that takes another function as argument, returns a function, or both. The whole array toolkit (map, filter, reduce, forEach) is built on this idea.
// Function as argument
const nums = [1, 2, 3, 4];
console.log(nums.map(n => n * 10)); // [10,20,30,40]
// Function as return value
const mult = factor => n => n * factor;
const triple = mult(3);
console.log(triple(5)); // 15
// Both — apply twice
const twice = fn => x => fn(fn(x));
console.log(twice(n => n + 1)(5)); // 7
2. What Is a Closure?
A closure is a function bundled with the lexical scope it was created in. When the outer function returns, its local variables would normally vanish — but if an inner function still references them, they stay alive, locked to that one inner function.
function makeCounter() {
let count = 0; // captured by the inner fn
return () => ++count; // closure
}
const c1 = makeCounter();
const c2 = makeCounter();
console.log(c1(), c1(), c1()); // 1 2 3
console.log(c2()); // 1 — separate state
makeCounter() call একটি স্বতন্ত্র count তৈরি করে — সেটি কেবল ভেতরের function দেখতে পায়।3. Private State via Closure
Closure is JavaScript's original encapsulation mechanism — long before # private class fields existed.
function makeAccount(initial) {
let balance = initial;
return {
deposit(n) { balance += n; },
withdraw(n) {
if (n > balance) throw new Error("insufficient");
balance -= n;
},
balance() { return balance; }
};
}
const a = makeAccount(100);
a.deposit(50);
console.log(a.balance()); // 150
console.log(a.balance); // undefined (private)
4. The Module Pattern (IIFE)
const theme = (() => {
let mode = "light";
return {
toggle() { mode = mode === "light" ? "dark" : "light"; },
get() { return mode; }
};
})();
theme.toggle();
console.log(theme.get()); // "dark"
5. The Famous Interview Question
What does this print?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 10);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 10);
}
The first loop logs 3 3 3 — every callback closes over the same i binding. The second loop logs 0 1 2 — let creates a fresh binding each iteration. This is the single best argument against var.
6. Memoization with a Closure
function memoize(fn) {
const cache = new Map();
return function (x) {
if (!cache.has(x)) cache.set(x, fn(x));
return cache.get(x);
};
}
function slowFib(n) {
if (n < 2) return n;
return slowFib(n - 1) + slowFib(n - 2);
}
const fastFib = memoize(slowFib);
console.log(fastFib(30)); // computed once, cached
console.log(fastFib(30)); // instant
7. Closure Scope Chain — Visualized
count long after makeCounter has returned.
8. Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| HOF | Higher-Order Function — takes or returns a function. | Higher-Order Function — function নেয় বা ফেরত দেয়। |
| Closure | Inner function + the lexical scope it was created in. | Inner function-এর সাথে তৈরির সময়ের scope সংরক্ষিত থাকা। |
| Capture | The inner function "remembers" outer variables by reference. | Inner function বাইরের variable-কে reference হিসেবে ধরে রাখে। |
| Module pattern | IIFE that returns an object with private state via closure. | IIFE যা closure দিয়ে private state রেখে object return করে। |
| Memoization | Caching function results by their input. | Input অনুযায়ী result cache করে রাখা। |
| Currying | Transforming f(a, b) into f(a)(b). | f(a, b) কে f(a)(b) তে রূপান্তর। |
| Partial application | Pre-fixing some arguments of a function. | Function-এর কিছু argument আগে থেকেই দেওয়া। |
| Compose / pipe | Combine functions to flow data through them. | Function-গুলোকে যুক্ত করে data এক প্রবাহে চালানো। |
makeCounter() call একটি স্বতন্ত্র count তৈরি করে; ভেতরের function সেটি দেখতে পায়। HOF আর closure মিলে memoize, debounce, throttle, partial, module pattern — আধুনিক JS-এর অনেক pattern সম্ভব করে।
9. Practice Problems
- Write
makeAdder(x)that returns a function addingxto its argument.✨ Show Answer
a1.jsconst makeAdder = x => y => x + y; const add5 = makeAdder(5); console.log(add5(3)); // 8 - Build a counter that supports increment, decrement, and reset.
✨ Show Answer
a2.jsfunction makeCounter(start = 0) { let n = start; return { inc() { return ++n; }, dec() { return --n; }, reset() { n = start; return n; } }; } const c = makeCounter(10); console.log(c.inc(), c.inc(), c.dec(), c.reset()); - Implement
compose(f, g)(x) === f(g(x)).✨ Show Answer
a3.jsconst compose = (f, g) => x => f(g(x)); const pipeline = compose(n => n * 2, n => n + 1); console.log(pipeline(5)); // (5+1)*2 = 12 - Implement
curryfor a 3-arg function.✨ Show Answer
a4.jsconst curry = fn => a => b => c => fn(a, b, c); const add3 = (x, y, z) => x + y + z; console.log(curry(add3)(1)(2)(3)); // 6 - Show the var-loop bug, then fix it three ways: let, IIFE, and forEach.
✨ Show Answer
a5.js// Bug for (var i = 0; i < 3; i++) setTimeout(() => console.log("bug", i), 0); // Fix 1: let for (let j = 0; j < 3; j++) setTimeout(() => console.log("let", j), 0); // Fix 2: IIFE for (var k = 0; k < 3; k++) ((idx) => setTimeout(() => console.log("iife", idx), 0))(k); // Fix 3: forEach [0,1,2].forEach(idx => setTimeout(() => console.log("each", idx), 0)); - Build a private "secret" holder where the secret can only be read with a correct password.
✨ Show Answer
a6.jsfunction vault(secret, pwd) { return attempt => attempt === pwd ? secret : "denied"; } const v = vault("42 is the answer", "hello"); console.log(v("x")); console.log(v("hello")); - Implement
partial(fn, ...preset)— like .bind without changing this.✨ Show Answer
a7.jsconst partial = (fn, ...preset) => (...rest) => fn(...preset, ...rest); const greet = (greeting, name) => `${greeting}, ${name}!`; const hi = partial(greet, "Hi"); console.log(hi("Arif")); - Build
throttle(fn, ms): ignore subsequent calls within ms.✨ Show Answer
a8.jsconst throttle = (fn, ms) => { let last = 0; return (...args) => { const now = Date.now(); if (now - last >= ms) { last = now; return fn(...args); } }; }; const log = throttle(msg => console.log("hit:", msg), 100); log("a"); log("b"); log("c"); // only "a" within 100 ms - Build
debounce(fn, ms): only fire after ms of silence.✨ Show Answer
a9.jsconst debounce = (fn, ms) => { let id; return (...args) => { clearTimeout(id); id = setTimeout(() => fn(...args), ms); }; }; const save = debounce(v => console.log("saved:", v), 200); save("a"); save("ab"); save("abc"); // only "abc" - Closure-based id generator: each call returns id 1, 2, 3...
✨ Show Answer
a10.jsconst nextId = (() => { let n = 0; return () => ++n; })(); console.log(nextId(), nextId(), nextId()); - Closure with multiple variables: write a "stopwatch" with start, stop, lap.
✨ Show Answer
a11.jsfunction stopwatch() { let t0 = null; return { start() { t0 = Date.now(); }, lap() { return Date.now() - t0; } }; } const sw = stopwatch(); sw.start(); for (let i = 0; i < 1e6; i++); console.log("ms:", sw.lap()); - Use a closure to track the number of times a function has been called.
✨ Show Answer
a12.jsconst spy = fn => { let calls = 0; const wrapped = (...args) => { calls++; return fn(...args); }; wrapped.calls = () => calls; return wrapped; }; const add = spy((a, b) => a + b); add(1, 2); add(3, 4); console.log(add.calls()); // 2 - Build
pipe(...fns)that returns a function applying them left-to-right.✨ Show Answer
a13.jsconst pipe = (...fns) => x => fns.reduce((v, f) => f(v), x); const p = pipe(n => n + 1, n => n * 2, n => n - 3); console.log(p(5)); // ((5+1)*2)-3 = 9 - Why is the closure pattern slower than a class with private
#fields?✨ Show Answer
Answer: Each call to a "factory" function creates fresh closures and fresh method functions, costing memory and a tiny bit of allocation. A class shares one method definition on its prototype across all instances. For tiny utilities the difference is invisible; for hot paths producing thousands of instances, classes win.
- Show that closures over a single variable share state, not copies.
✨ Show Answer
a15.jsfunction pair() { let n = 0; return [() => ++n, () => n]; } const [inc, peek] = pair(); inc(); inc(); inc(); console.log(peek()); // 3 — shared - Use a closure to make a once-per-event handler.
✨ Show Answer
a16.jsconst once = fn => { let fired = false; return (...a) => { if (!fired) { fired = true; return fn(...a); } }; }; const handler = once(() => console.log("clicked")); handler(); handler(); handler(); - In one paragraph, explain a closure to a friend who knows Python.
✨ Show Answer
Answer: A closure is a function plus the local variables it remembers from where it was defined. Python has the same idea — a nested function can refer to a variable from its enclosing function and that variable stays alive even after the outer function returns. JavaScript's
let/constsimply make this work cleanly per scope, with no extranonlocalkeyword required to mutate; you just close over the binding. - Build a tiny event bus:
on(name, fn),emit(name, data).✨ Show Answer
a18.jsconst bus = (() => { const map = {}; return { on(name, fn) { (map[name] ??= []).push(fn); }, emit(name, data) { (map[name] || []).forEach(f => f(data)); } }; })(); bus.on("hi", d => console.log("got:", d)); bus.emit("hi", "hello");
Summary — Module 12
Higher-order functions accept or return functions — they're the foundation of declarative array work. Closures are simply inner functions that keep their outer scope alive. Together, they enable counters, factories, memoization, debounce/throttle, and the module pattern. The classic var+setTimeout bug is a closure being asked to remember one shared binding instead of three different ones.