Variables: var, let, const & Scope

JS-এর সবচেয়ে গুরুত্বপূর্ণ একক নিয়ম

~30 min Beginner 12 practice problems Live runner

1. Three Ways to Declare a Variable

Modern JS gives you three keywords. Only two of them should appear in your code in 2026.

KeywordReassignable?ScopeHoisted asUse
constNoBlockuninitialized (TDZ)Default
letYesBlockuninitialized (TDZ)When you must reassign
varYesFunctionundefinedNever (legacy only)
The single rule Use const by default. Switch to let only when reassignment is required. Never write var in new code.
তিনটি keyword থাকলেও আধুনিক JS-এ var ব্যবহার করবেন না। সর্বদা const default — শুধু পুনরায় assign করতে হলে let।

2. The var Trap — Function Scope & Hoisting

var is function-scoped: a var declared inside an if or for leaks to the entire enclosing function. It is also hoisted as undefined — accessible before the line you wrote.

var-trap.js
// Hoisted as undefined — no error before declaration
console.log(x);    // undefined
var x = 5;
console.log(x);    // 5

// Function-scoped: leaks out of the if block
function demo() {
    if (true) {
        var leak = "oops";
    }
    console.log(leak); // "oops"  ← still accessible!
}
demo();

3. let & const — Block Scope

Block scope means a variable declared inside { } is invisible outside it. This matches how almost every other modern language behaves.

block.js
{
    const a = 10;
    let   b = 20;
    console.log(a, b);   // 10 20
}
// console.log(a);  → ReferenceError

for (let i = 0; i < 3; i++) {
    // each iteration gets its OWN i — closures friendly
}
// console.log(i);  → ReferenceError

4. The Temporal Dead Zone (TDZ)

let/const are also "hoisted" — but unlike var, they sit in the TDZ from the start of the block until the actual declaration. Accessing them in the TDZ is a hard error.

tdz.js
try {
    console.log(y);   // throws ReferenceError
} catch (e) {
    console.log("caught:", e.message);
}
let y = 5;
console.log(y);       // 5
TDZ মানে — let/const declare করা আগেই access করতে গেলে error। var-এর মতো undefined দেয় না — এটি ভালো, কারণ silent bug-কে prevent করে।

5. const — What's Actually Constant?

const means the binding can't be reassigned. The value can still mutate if it's an object.

const.js
const n = 5;
// n = 6;  → TypeError

const arr = [1, 2];
arr.push(3);              // allowed — content mutated
console.log(arr);          // [1, 2, 3]
// arr = [9];  → TypeError

const obj = { x: 1 };
obj.x = 99;                // allowed
obj.y = 100;               // allowed
console.log(obj);          // { x: 99, y: 100 }

// True deep-immutable: Object.freeze (shallow only)
const frozen = Object.freeze({ a: 1 });
frozen.a = 2;
console.log(frozen.a);     // 1 (silently ignored in non-strict)

6. Lexical Scope & Scope Chain

Lexical means scope is determined by where the code is written, not where it's called from. An inner function can see all variables of every outer function it is nested in — that chain is the scope chain.

lexical.js
const country = "Bangladesh";          // outer

function outer() {
    const city = "Dhaka";             // middle
    function inner() {
        const area = "Mirpur";        // inner
        console.log(area, city, country);
    }
    inner();
}
outer();   // Mirpur Dhaka Bangladesh

7. Global Scope Dangers

Anything declared at the top level without any keyword (or with var) becomes a property of the global object (window in browsers). This is the source of countless conflicts in the wild.

Use strict mode Either start every file with "use strict"; or work inside an ES Module (which is automatically strict). In strict mode, undeclared assignments throw instead of silently creating globals.

8. Glossary (শব্দকোষ)

TermMeaningবাংলায়
varLegacy keyword — function-scoped, hoisted as undefined.পুরোনো keyword — function-scoped এবং hoisting bug-প্রবণ।
letBlock-scoped, reassignable variable. TDZ-protected.Block-scoped, পুনরায় assign করা যায়; TDZ-সুরক্ষিত।
constBlock-scoped binding that cannot be reassigned.Block-scoped, পুনরায় assign করা যায় না।
HoistingDeclarations are conceptually moved to the top of their scope.Declaration বুঝে নিয়ে scope-এর শুরুতে সরানোর engine-এর কৌশল।
TDZTemporal Dead Zone — accessing let/const before declaration throws.Declaration-এর আগে let/const access করলে error।
Block scopeVariables only visible inside the { } they were declared in.যে { }-এ declare হয়েছে শুধু সেখানেই দৃশ্যমান।
Lexical scopeScope determined by where the code is written, not called.Code কোথায় লেখা হয়েছে তার উপর scope নির্ভর করে।
Strict mode"use strict" — enables stricter parsing and error rules."use strict" — কঠোর parsing ও error নিয়ম।
একটি নিয়ম যথেষ্ট: const default; পুনরায় assign করতে হলে let; var কখনোই না। Block scope এবং TDZ আপনার silent bug ধরিয়ে দেয়। ES module-এ strict mode automatically চালু থাকে — তাই module-এ "use strict" লেখার দরকার নেই।

9. Practice Problems

  1. Show that var hoists as undefined but let throws.
    ✨ Show Answer
    a1.js
    console.log(a);  // undefined
    var a = 10;
    
    try { console.log(b); }
    catch (e) { console.log("err:", e.message); }
    let b = 20;
  2. Demonstrate that const on an array still allows push.
    ✨ Show Answer
    a2.js
    const arr = [1, 2];
    arr.push(3);
    arr[0] = 9;
    console.log(arr); // [9, 2, 3]
  3. Predict the output: a for loop with var + setTimeout printing i. Then fix it with let.
    ✨ Show Answer

    With var, every callback closes over the same i (final value 3). With let, each iteration gets a fresh binding.

    a3.js
    for (var i = 0; i < 3; i++)
        setTimeout(() => console.log("var:", i), 0);
    
    for (let j = 0; j < 3; j++)
        setTimeout(() => console.log("let:", j), 0);
  4. Write a function that creates an inner counter using lexical scope.
    ✨ Show Answer
    a4.js
    function makeCounter() {
        let n = 0;
        return () => ++n;
    }
    const c = makeCounter();
    console.log(c(), c(), c()); // 1 2 3
  5. Use Object.freeze to make a config object truly immutable.
    ✨ Show Answer
    a5.js
    "use strict";
    const CONFIG = Object.freeze({ apiUrl: "/api", retries: 3 });
    try { CONFIG.retries = 5; }
    catch (e) { console.log("blocked:", e.message); }
    console.log(CONFIG);
  6. Show that let in a for loop creates one binding per iteration.
    ✨ Show Answer
    a6.js
    const fns = [];
    for (let k = 0; k < 3; k++) {
        fns.push(() => k);
    }
    console.log(fns.map(f => f())); // [0, 1, 2]
  7. In one paragraph, explain why var should never appear in modern code.
    ✨ Show Answer

    Answer: var is function-scoped, hoisted as undefined, and silently re-declarable — three behaviours that conspire to produce ghost bugs that only show up far from where the variable was written. let and const are block-scoped, throw on TDZ access, and refuse silent re-declaration, so most accidents become visible at the line that caused them. Linters and toolchains in 2026 default to forbidding var.

    var function-scoped, hoisted-as-undefined এবং re-declarable — তিনটি বৈশিষ্ট্য একসাথে এমন bug তৈরি করে যা declaration থেকে অনেক দূরে দেখা দেয়। let/const এসব সমস্যা দূর করে।

  8. Show that let in a block is invisible outside.
    ✨ Show Answer
    a8.js
    {
        let hidden = 42;
        console.log("inside:", hidden);
    }
    try { console.log(hidden); }
    catch (e) { console.log("outside:", e.message); }
  9. Predict the output of three nested functions accessing an outer const.
    ✨ Show Answer
    a9.js
    const g = "global";
    function a() {
        const m = "middle";
        function b() {
            const i = "inner";
            console.log(g, m, i);
        }
        b();
    }
    a(); // global middle inner
  10. Show that re-declaring let x in the same scope is a SyntaxError.
    ✨ Show Answer

    You can't catch a SyntaxError at runtime — it's detected at parse time. Try copying this into the browser console:

    a10.js
    // SyntaxError when parsed:
    // let z = 1;
    // let z = 2;
    
    // var allows it (silently!):
    var w = 1;
    var w = 2;
    console.log(w); // 2 — silent overwrite, classic var bug
  11. Build a tiny "module" using an IIFE and a closure to keep state private.
    ✨ Show Answer
    a11.js
    const wallet = (() => {
        let balance = 0;
        return {
            deposit(n) { balance += n; },
            balanceOf() { return balance; }
        };
    })();
    wallet.deposit(100);
    wallet.deposit(50);
    console.log(wallet.balanceOf()); // 150
    console.log(wallet.balance);     // undefined (private)
  12. Why is "use strict" usually unnecessary inside an ES module?
    ✨ Show Answer

    Answer: ES Modules are strict by default. Anything loaded with <script type="module"> or imported in Node runs in strict mode automatically — silent globals throw, this is undefined at the top level, and reserved words can't be used as identifiers.

    ES module-এ "use strict" লেখার দরকার নেই — module-এ এটি default-ভাবেই enabled থাকে।

Summary — Module 06

Use const by default, let when reassignment is required, never var. let and const are block-scoped and protected by the TDZ. Lexical scope means inner functions see all enclosing variables. Always work in strict mode (or ES modules, which are strict by default).

নিয়ম একটাই — const default, let দরকারে, var কখনোই না। Block scope এবং TDZ দুই-ই আপনার মিত্র।

Next Module → Strings, Template Literals & Unicode — Bangla চরিত্রের রহস্য সহ।