Closures and Scope
1. What is lexical scope in JavaScript?
Lexical scope মানে হলো — একটি variable কোথায় access করা যাবে, সেটা নির্ধারিত হয় সেই variable টি code-এ কোথায় লেখা হয়েছে তার উপর ভিত্তি করে, runtime-এ কোথায় call হচ্ছে তার উপর নয়।
সহজ কথায়: "কোড লেখার সময়ই scope নির্ধারিত হয়ে যায়।"
JavaScript-এ প্রতিটি function বা block তার চারপাশের (outer/parent) scope-এর variable গুলো access করতে পারে। এটাই lexical scoping।
Code ExampleJavaScriptClick to view details
function outer() {
const message = "Hello from outer!"; // outer scope-এ define করা
function inner() {
console.log(message); // ✅ inner function, outer-এর variable access করতে পারে
}
inner();
}
outer(); // Output: "Hello from outer!"
এখানে inner function টি message variable-টি নিজের scope-এ না থাকলেও, lexical parent (অর্থাৎ outer) থেকে সেটি access করতে পারছে।
🔍 Scope Chain
Lexical scope কাজ করে scope chain তৈরির মাধ্যমে। কোনো variable খুঁজে না পেলে JavaScript inner → outer → global — এই ক্রমে উপরে উঠতে থাকে।
⚠️ Lexical Scope বনাম Dynamic Scope
| বিষয় | Lexical Scope | Dynamic Scope |
|---|---|---|
| Scope নির্ধারিত হয় | লেখার সময় (compile time) | call করার সময় (runtime) |
| ভিত্তি | Code-এর structure | Call stack |
| উদাহরণ | JavaScript, Python | Bash, বিশেষ কিছু Lisp |
🔗 Closure-এর সাথে সম্পর্ক
Lexical scope-এর কারণেই closure সম্ভব। একটি function তার outer scope-এর variable মনে রাখে, এমনকি সেই outer function শেষ হয়ে যাওয়ার পরেও।
Code ExampleJavaScriptClick to view details
function makeCounter() {
let count = 0; // এই variable টি closure-এ "ধরা" থাকে
return function () {
count++;
console.log(count);
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// makeCounter() শেষ হয়ে গেছে, কিন্তু count এখনো accessible!
"Function যেখানে defined, সেই জায়গার scope-ই তার lexical scope — যেখানে called সেটা নয়।"
How is scope determined at compile time?
JavaScript engine code execute করার আগে দুটি ধাপে কাজ করে:
Code ExampleClick to view details
Source Code
↓
┌─────────────────────────────────────┐
│ Phase 1: COMPILATION / PARSING │
│ • Tokenization │
│ • AST (Abstract Syntax Tree) তৈরি │
│ • Scope নির্ধারণ ✅ │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Phase 2: EXECUTION │
│ • Code line by line চলে │
│ • Variable-এ value assign হয় │
└─────────────────────────────────────┘
Compilation phase-এ engine প্রতিটি variable ও function declaration খুঁজে বের করে এবং সেগুলো কোন scope-এ আছে সেটা নির্দিষ্ট করে রাখে।
Code ExampleJavaScriptClick to view details
function outer() {
var x = 10; // ← compile time-এ engine জানে: x, outer-এর scope-এ
function inner() {
var y = 20; // ← compile time-এ engine জানে: y, inner-এর scope-এ
console.log(x); // ← x কোথায় আছে? outer-এ — compile time-এই জানা
}
}
var vs let/const — Hoisting-এর পার্থক্য
var | let / const | |
|---|---|---|
| Hoisting | হয় — undefined দিয়ে | হয় — কিন্তু TDZ-এ থাকে |
| Scope | Function scope | Block scope |
| Compile time-এ | Declaration register হয় | Declaration register হয় |
console.log(a); // undefined (hoisted)
var a = 5;
console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 10; // TDZ (Temporal Dead Zone)-এ থাকে
What is the scope chain?
Scope chain হলো একটি সংযুক্ত chain যা দিয়ে JavaScript variable খোঁজে — প্রথমে current scope, না পেলে outer scope, তারপর আরও উপরে — যতক্ষণ না global scope পর্যন্ত পৌঁছায়।
Code ExampleClick to view details
┌──────────────────────────────┐
│ Global Scope │ ← সবচেয়ে উপরে
│ globalVar = "I'm global" │
└──────────────┬───────────────┘
│ (outer reference)
┌──────────────▼───────────────┐
│ outer() Scope │
│ outerVar = "I'm outer" │
└──────────────┬───────────────┘
│ (outer reference)
┌──────────────▼───────────────┐
│ inner() Scope │ ← সবচেয়ে নিচে (current)
│ innerVar = "I'm inner" │
└──────────────────────────────┘
Code ExampleJavaScriptClick to view details
const globalVar = "Global";
function outer() {
const outerVar = "Outer";
function inner() {
const innerVar = "Inner";
// Scope chain বরাবর উপরে খোঁজে
console.log(innerVar); // ✅ নিজের scope-এ পেয়েছে
console.log(outerVar); // ✅ outer-এ পেয়েছে
console.log(globalVar); // ✅ global-এ পেয়েছে
console.log(unknown); // ❌ ReferenceError — কোথাও নেই
}
inner();
}
outer();
Variable Resolution-এর ক্রম:
inner scope → outer scope → global scope → ReferenceError ❌
গুরুত্বপূর্ণ: Scope chain শুধু উপরে যায়, কখনো নিচে যায় না।
How does nested scope work?
Nested scope মানে হলো একটি scope-এর ভেতরে আরেকটি scope থাকা। প্রতিটি function বা {} block নিজের আলাদা scope তৈরি করে।
Code ExampleJavaScriptClick to view details
// Level 0: Global Scope
const app = "MyApp";
function level1() {
// Level 1: Function Scope
const user = "Karim";
function level2() {
// Level 2: Nested Function Scope
const role = "admin";
if (true) {
// Level 3: Block Scope (let/const এর জন্য)
const temp = "temporary";
// ✅ সব উপরের level access করা যাবে
console.log(app); // "MyApp"
console.log(user); // "Karim"
console.log(role); // "admin"
console.log(temp); // "temporary"
}
// console.log(temp); // ❌ Block এর বাইরে temp নেই
}
level2();
// console.log(role); // ❌ level2-এর scope দেখা যাচ্ছে না
}
level1();
Shadowing — একই নামের Variable
Inner scope-এ যদি outer scope-এর মতো একই নামের variable থাকে, তাহলে inner টি outer টিক ে shadow করে:
Code ExampleJavaScriptClick to view details
const color = "red"; // outer
function paintWall() {
const color = "blue"; // inner — outer "red" কে shadow করছে
console.log(color); // "blue" — inner-টাই দেখা যাচ্ছে
}
paintWall();
console.log(color); // "red" — global এখনো অপরিবর্তিত
2. What is a closure and how does it work?
Closure হলো এমন একটি function যেটি তার outer (lexical) scope-এর variablesগুলো মনে রাখে — এমনকি সেই outer function-এর execution শেষ হয়ে যাওয়ার পরেও।
সহজ কথায়: "Function + তার জন্মস্থানের Scope = Closure"
Code ExampleJavaScriptClick to view details
function outer() {
const message = "আমি outer-এর variable!";
function inner() {
// inner function, outer-এর variable "মনে রেখেছে"
console.log(message);
}
return inner; // function টা return করা হচ্ছে
}
const remembers = outer(); // outer() এখানে শেষ হয়ে যায়
remembers(); // কিন্তু তবুও Output: "আমি outer-এর variable!"
outer()শেষ হয়ে গেলেওmessagevariable টি garbage collect হয়নি — কারণinnerfunction টির একটি closure আছে সেই variable-এর উপর।
How does a function “remember” variables from its outer scope?
এটি সম্ভব হয় কারণ JavaScript engine প্রতিটি function তৈরির সময় সেই function-এর সাথে একটি [[Environment]] (internal hidden property) যুক্ত করে দেয়। এই [[Environment]]-এ সেই function-এর lexical scope-এর reference সংরক্ষিত থাকে।
Code ExampleClick to view details
Function Object তৈরি হওয়ার সময়:
┌────────────────────────────────────┐
│ inner (Function Object) │
│ │
│ code: "console.log(message)" │
│ [[Environment]] ──────────────────┼───► outer-এর Variable Environment
└────────────────────────────────────┘ │
▼
┌────────────────────────┐
│ { message: "Hello!" } │
└────────────────────────┘
Code ExampleJavaScriptClick to view details
function createGreeter(name) {
// name → [[Environment]]-এ store হয়
return function greet() {
// greet-এর [[Environment]] → createGreeter-এর scope-এর reference ধরে রাখে
console.log(`Hello, ${name}!`);
};
}
const greetKarim = createGreeter("Karim");
const greetRahim = createGreeter("Rahim");
// প্রতিটি closure-এর নিজস্ব আলাদা [[Environment]] আছে
greetKarim(); // Hello, Karim!
greetRahim(); // Hello, Rahim!
প্রতিটি createGreeter() call-এ আলাদা closure তৈরি হয় — আলাদা name-সহ।
What happens to variables after the outer function returns?
সাধারণত কোনো function তার execution শেষ করলে তার local variables গুলো Call Stack থেকে সরিয়ে Garbage Collected হয়। কিন্তু closure-এর ক্ষেত্রে এই নিয়মের ব্যতিক্রম ঘটে।
Code ExampleClick to view details
সাধারণ ক্ষেত্রে (closure ছাড়া):
──────────────────────────────────────────────
outer() call হলো
↓
local variable তৈরি হলো (Stack-এ)
↓
outer() return করলো
↓
local variable → ❌ Garbage Collected (মুছে যায়)
Closure-এর ক্ষেত্রে:
──────────────────────────────────────────────
outer() call হলো
↓
local variable তৈরি হলো (Heap-এ move হয়)
↓
inner function টি [[Environment]]-এ variable-এর reference রাখলো
↓
outer() return করলো
↓
local variable → ✅ Heap-এ বেঁচে থাকে (reference আছে বলে)
↓
inner function যতদিন alive, variable ততদিন alive
Code ExampleJavaScriptClick to view details
function makeCounter() {
let count = 0; // outer function return করার পরেও বেঁচে থাকবে
return {
increment() { count++; console.log(count); },
decrement() { count--; console.log(count); },
reset() { count = 0; console.log("Reset!"); }
};
}
const counter = makeCounter(); // makeCounter() এখানে শেষ
counter.increment(); // 1 ← count এখনো জীবিত!
counter.increment(); // 2
counter.increment(); // 3
counter.decrement(); // 2
counter.reset(); // Reset!
counter.increment(); // 1
How are closures stored in memory?
JavaScript engine দুটি জায়গায় data রাখে:
| Call Stack | Heap Memory | |
|---|---|---|
| রাখে | Function execution context, primitive values | Objects, closures, functions |
| কখন মুছে যায় | Function return করলেই | কোনো reference না থাকলে (GC) |
| আকার | ছোট, সীমিত | বড়, dynamic |
Closure-এর Memory Model:
Code ExampleClick to view details
CALL STACK HEAP MEMORY
────────────────── ─────────────────────────────────────
│ Global Context │ │ │
│ │ │ ┌─────────────────────────┐ │
│ counter ──────┼────────────┼──►│ counter (Object) │ │
│ │ │ │ increment: fn ─────┐ │ │
└────────────────┘ │ │ decrement: fn ──┐ │ │ │
│ │ reset: fn ─┐│ │ │ │
│ └─────────────────┼┼───┘ │ │
│ │││ │ │
│ ┌─────────────────▼▼▼──────┐ │
│ │ Closure Environment │ │
│ │ { count: 1 } ← shared! │ │
│ └───────────────────────────┘ │
└───────────────────────────────────┘
তিনটি method (increment, decrement, reset) একই closure environment share করে — তাই count সবার কাছে একই।
Garbage Collection ও Closure:
Code ExampleJavaScriptClick to view details
function heavyClosure() {
const bigData = new Array(1000000).fill("data"); // বড় data
return function () {
console.log(bigData.length); // bigData-এর reference ধরে রেখেছে
};
}
let fn = heavyClosure();
fn(); // 1000000 → bigData এখনো memory-তে আছে
fn = null; // ← reference সরিয়ে দিলাম
// এখন bigData → ❌ Garbage Collected হবে (কোনো reference নেই)
⚠️ Memory Leak সতর্কতা: Closure অপ্রয়োজনীয়ভাবে বড় data ধরে রাখলে memory leak হতে পারে। Reference সরিয়ে দিলে Garbage Collector সেই memory মুক্ত করে।
3. What are real-world use cases of closures?
How are closures used in data privacy (encapsulation)?
Encapsulation মানে হলো — data লুকিয়ে রাখা এবং শুধুমাত্র নির্দিষ্ট interface-এর মাধ্যমে সেটি access করতে দেওয়া। Closure দিয়ে JavaScript-এ private variable তৈরি করা যায় — যা বাইরে থেকে directly access বা modify করা সম্ভব না।
Code ExampleJavaScriptClick to view details
// ❌ Closure ছাড়া — সব কিছু exposed, যে কেউ balance বদলাতে পারে
const account = {
balance: 1000,
owner: "Karim"
};
account.balance = 999999; // ⚠️ যে কেউ হাত দিতে পারছে!
// ✅ Closure দিয়ে — balance সম্পূর্ণ private
function createBankAccount(initialBalance, owner) {
let balance = initialBalance; // 🔒 private — বাইরে visible না
let transactionHistory = []; // 🔒 private
return {
deposit(amount) {
if (amount <= 0) return console.log("Invalid amount!");
balance += amount;
transactionHistory.push(`+${amount}`);
console.log(`Deposited ${amount}. Balance: ${balance}`);
},
withdraw(amount) {
if (amount > balance) return console.log("Insufficient funds!");
balance -= amount;
transactionHistory.push(`-${amount}`);
console.log(`Withdrawn ${amount}. Balance: ${balance}`);
},
getBalance() {
return balance; // শুধু read করতে পারবে, directly set করতে পারবে না
},
getHistory() {
return [...transactionHistory]; // copy দেওয়া হচ্ছে, original নয়
},
getOwner() {
return owner;
}
};
}
const myAccount = createBankAccount(1000, "Karim");
myAccount.deposit(500); // Deposited 500. Balance: 1500
myAccount.withdraw(200); // Withdrawn 200. Balance: 1300
console.log(myAccount.getBalance()); // 1300
// বাইরে থেকে balance access করার চেষ্টা:
console.log(myAccount.balance); // undefined ← protected!
myAccount.balance = 999999; // কোনো effect নেই!
console.log(myAccount.getBalance()); // এখনো 1300 ✅
How are they used in function factories?
Function factory হলো এমন একটি function যেটি অন্য function তৈরি করে — এবং প্রতিটি তৈরি হওয়া function তার নিজস্ব closure-এ কিছু pre-configured state নিয়ে থাকে।
Code ExampleJavaScriptClick to view details
// ✅ Function Factory: Multiplier তৈরি করে
function createMultiplier(factor) {
// factor টি closure-এ ধরা থাকে
return (number) => number * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const tenTimes = createMultiplier(10);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(tenTimes(5)); // 50
// প্রতিটি function-এর নিজস্ব আলাদা closure আছে
console.log(double(7)); // 14
console.log(triple(7)); // 21
Real-World উদাহরণ — API Request Factory:
Code ExampleJavaScriptClick to view details
function createApiClient(baseURL, authToken) {
// baseURL ও authToken closure-এ থাকে — বারবার দেওয়া লাগবে না
const defaultHeaders = {
"Content-Type": "application/json",
"Authorization": `Bearer ${authToken}`
};
async function request(method, endpoint, body = null) {
const response = await fetch(`${baseURL}${endpoint}`, {
method,
headers: defaultHeaders,
body: body ? JSON.stringify(body) : null
});
return response.json();
}
// Pre-configured HTTP methods return করছে
return {
get: (endpoint) => request("GET", endpoint),
post: (endpoint, data) => request("POST", endpoint, data),
put: (endpoint, data) => request("PUT", endpoint, data),
delete: (endpoint) => request("DELETE", endpoint)
};
}
// প্রতিটি client-এর আলাদা closure — আলাদা baseURL ও token
const productionApi = createApiClient("https://api.myapp.com", "prod-token-xyz");
const stagingApi = createApiClient("https://staging.myapp.com", "staging-token-abc");
// ব্যবহার করতে token বা URL আর দিতে হচ্ছে না!
productionApi.get("/users");
productionApi.post("/users", { name: "Karim" });
stagingApi.get("/products");
How are closures used in event handlers?
Event handler-এ closure ব্যবহার করা হয় যখন handler function-টার কাছে event তৈরির সময়কার কিছু context (data) রাখা দরকার হয়।
Code ExampleJavaScriptClick to view details
// ❌ Closure ছাড়া — কোন button click হলো বোঝা যাচ্ছে না
function handleClick() {
console.log("A button was clicked"); // কোনটা?
}
// ✅ Closure দিয়ে — প্রতিটি handler নিজের data মনে রাখছে
function createButtonHandler(buttonId, label) {
let clickCount = 0; // প্রতিটি button-এর নিজস্ব আলাদা counter
return function handleClick(event) {
clickCount++;
console.log(`Button "${label}" (id: ${buttonId}) — clicked ${clickCount} times`);
console.log("Event target:", event.target);
};
}
const buttons = [
{ id: "btn-1", label: "Submit" },
{ id: "btn-2", label: "Cancel" },
{ id: "btn-3", label: "Delete" }
];
// প্রতিটি button-এর জন্য আলাদা handler তৈরি হচ্ছে, আলাদা closure-এ
buttons.forEach(({ id, label }) => {
const button = document.getElementById(id);
button.addEventListener("click", createButtonHandler(id, label));
});
// Submit click করলে: Button "Submit" (id: btn-1) — clicked 1 times
// Submit আবার click: Button "Submit" (id: btn-1) — clicked 2 times
// Cancel click করলে: Button "Cancel" (id: btn-2) — clicked 1 times ← আলাদা counter!
Debounce Function — Closure-এর Practical Power:
Code ExampleJavaScriptClick to view details
// Search input-এ প্রতিটি keystroke-এ API call না করে
// user টাইপ থামার পর একটিই call করো
function debounce(fn, delay) {
let timeoutId; // closure-এ থাকে — প্রতিটি call-এ shared
return function (...args) {
clearTimeout(timeoutId); // আগের timer বাতিল
timeoutId = setTimeout(() => { // নতুন timer শুরু
fn.apply(this, args);
}, delay);
};
}
async function searchProducts(query) {
console.log(`API call: searching for "${query}"`);
// const results = await fetch(`/api/search?q=${query}`);
}
// 500ms এর মধ্যে আবার টাইপ করলে আগের API call বাতিল হবে
const debouncedSearch = debounce(searchProducts, 500);
// User দ্রুত টাইপ করলে:
debouncedSearch("n"); // timer শুরু → বাতিল
debouncedSearch("no"); // timer শুরু → বাতিল
debouncedSearch("nod"); // timer শুরু → বাতিল
debouncedSearch("node"); // timer শুরু → 500ms পরে চলে
// API call: searching for "node" ← শুধু একটিই!
4. Closures in Loops — Common Pitfalls
var কেন Loop-এ Async Code-এ ভুল করে?
সমস্যাটা বোঝার জন্য আগে var-এর function scope সম্পর্কে জানতে হবে।
// var — function scope (block এর ভেতরেও থাকে না!)
for (var i = 0; i < 3; i++) {
// এই i টি loop-এর ভেতরে নয়, বাইরের function scope-এ থাকে
}
console.log(i); // 3 ← loop শেষেও accessible!
😱 বিখ্যাত Bug:
Code ExampleJavaScriptClick to view details
// ❌ var দিয়ে loop + setTimeout
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // কী দেখাবে বলে মনে হয়?
}, 1000);
}
// আশা: 0, 1, 2
// বাস্তব: 3, 3, 3 😱
কেন 3, 3, 3 আসছে?
কারণটা হলো closure + var-এর function scope এর সমন্বয়:
Code ExampleClick to view details
Loop চলার সময় কী হচ্ছে:
─────────────────────────────────────────────────────
i = 0 → setTimeout register হলো (1000ms পরে চলবে)
i = 1 → setTimeout register হল ো (1000ms পরে চলবে)
i = 2 → setTimeout register হলো (1000ms পরে চলবে)
i = 3 → loop শেষ (i++ → i = 3, condition false)
─────────────────────────────────────────────────────
...1000ms পার হলো...
─────────────────────────────────────────────────────
Callback 1 চলে → console.log(i) → i এখন 3 → "3"
Callback 2 চলে → console.log(i) → i এখন 3 → "3"
Callback 3 চলে → console.log(i) → i এখন 3 → "3"
তিনটি callback তিনটি আলাদা i দেখছে না — সব কটি একই i variable-এর reference ধরে আছে। এবং যখন callback চলে, ততক্ষণে i এর মান 3 হয়ে গেছে।
Code ExampleClick to view details
Memory-তে var দিয়ে:
┌─────────────────────────────────────────┐
│ Function/Global Scope │
│ │
│ i = 0 → 1 → 2 → 3 (একটিই variable) │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ cb1 ─┘ cb2 ─┘ cb3 ─┘ │
│ (তিনটি callback একই i-কে point করে) │
└─────────────────────────────────────────┘