Difference between let, const, and var
Advertisement
🧩 Scenario
Architecture Walkthrough
Scope: Block vs Function
var is function-scoped. A variable declared with var inside an if block or a for loop is accessible anywhere within the enclosing function, or globally if declared at the top level. This scope leakage is a frequent source of bugs because the variable outlives the block that created it.
let and const are block-scoped. A variable declared inside any pair of curly braces (if, for, while, or any bare block) is only accessible within that block. Trying to access it outside throws a ReferenceError. This containment is intentional and makes code easier to reason about because a variable's lifespan matches the block where it was declared.
const Does Not Mean Immutable
const prevents reassignment of the variable binding. It does not make the value immutable. For primitive values, this distinction is irrelevant: you cannot reassign const n = 5 to a different number, and primitives have no properties to mutate. For objects and arrays, however, the binding is constant but the contents are not.
A const array can have elements pushed, removed, or modified. A const object can have properties added, changed, or deleted. If you need a truly immutable object, you must use Object.freeze(), and even then the freeze is shallow. This distinction trips up many developers who expect const to do more than it does.
The Loop Closure Bug with var
One of the most classic JavaScript bugs involves var inside a loop combined with asynchronous callbacks. Because var is function-scoped, all iterations of the loop share the same variable. By the time any async callback runs, the loop has finished and the variable holds its final value.
let fixes this because it creates a new binding for each iteration of the loop. Each closure captures a distinct scope, so async callbacks each see the value the variable held during their specific iteration. This behavior difference is a reliable interview topic because it demonstrates understanding of both scoping and closures.
Key Code Explained
// Block scope difference
if (true) {
var a = 10; // function-scoped, leaks out
let b = 20; // block-scoped, stays inside
const c = 30; // block-scoped, stays inside
}
console.log(a); // 10
console.log(b); // ReferenceError
console.log(c); // ReferenceError
// const does not mean immutable for objects
const user = { name: 'Ghazi' };
user.name = 'Ali'; // allowed — mutating the object, not reassigning the binding
user = {}; // TypeError — reassigning the binding is not allowed
// Loop closure bug: var shares one binding across iterations
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Prints: 3, 3, 3 — all closures share the same i
// let fix: new binding per iteration
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// Prints: 0, 1, 2 — each closure captures its own j
The loop closure examples are the most instructive comparison. They show both the scoping and closure behavior in a realistic scenario, and the difference in output is dramatic enough to make the rule memorable.
Tradeoffs
| Keyword | Scope | Reassignable | Re-declarable | Hoisted |
|---|---|---|---|---|
| var | Function | Yes | Yes | Yes (as undefined) |
| let | Block | Yes | No | Yes (TDZ) |
| const | Block | No | No | Yes (TDZ) |
What Interviewers Actually Check
- Whether you know
varis function-scoped andlet/constare block-scoped - Whether you can explain the loop closure bug and why
letfixes it - Whether you understand that
constprevents reassignment but not mutation - Whether you know the temporal dead zone applies to
letandconstbut notvar - Whether you can state a clear default recommendation and explain when to switch from
consttolet
Follow-Up Questions
- If you need a truly immutable object in JavaScript, what must you do beyond using
const? - How does TypeScript's
readonlykeyword relate to whatconstdoes for objects? - In a module that exports a
constobject, can the importing module mutate its properties? - Why was
varnot simply removed from the language whenletandconstwere introduced? - A senior engineer proposes a lint rule that forbids all uses of
letand requiresconsteverywhere. What would break, and is it a good rule?
Common Candidate Mistakes
- Thinking
constmakes an object immutable when it only prevents reassignment of the binding - Using
varinside a loop and being surprised that all closures share the same variable - Trying to re-declare a
letvariable in the same scope and not expecting aSyntaxError - Confusing the hoisting behavior of
var(initialized toundefined) withletandconst(TDZ) - Using
varin a component or module expecting function scope while inadvertently creating a broader shared binding
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you explain the scoping difference between
varandlet/constwith a block-level example? - Can you explain why
constdoes not make an object immutable and demonstrate it with code? - Can you explain the classic loop closure bug with
varand howletfixes it? - Can you state the default recommendation and justify when
letis needed instead ofconst? - Can you name the hoisting behavior difference between
varandlet/const?
Summary
var, let, and const differ primarily in scope and reassignment rules. var is function-scoped, hoisted with an initial value of undefined, and can be re-declared freely. let and const are block-scoped, hoisted but not initialized (creating the temporal dead zone), and cannot be re-declared in the same scope.
const prevents reassignment of the binding, but it does not prevent mutation of the value. An array declared with const can still be pushed to. An object declared with const can still have its properties changed. Only Object.freeze() provides actual immutability, and even then only at one level deep.
The default modern recommendation is to use const for everything that does not need reassignment, and switch to let only when you genuinely need to reassign. Avoid var in all new code. This convention makes intent explicit: a const signals the binding will not change, which helps both the reader and the optimizer.
Which should I use most often?
Prefer const by default, and use let only when you need to reassign the variable.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement