Difference between let, const, and var

Beginner8 min interview
Skills tested:
Block scope vs function scopeTemporal dead zone for let and constconst immutability vs object mutability distinctionvar re-declaration and redefinition behaviorLoop variable capture and closure behavior

Advertisement

🧩 Scenario

In a real codebase, you will encounter this every time you declare a variable. The choice between const and let affects how clearly code communicates intent, and using var in modern code can introduce subtle bugs around scoping in loops, conditionals, and asynchronous callbacks. This question is foundational because it underlies closures, the temporal dead zone, and object mutability.

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

KeywordScopeReassignableRe-declarableHoisted
varFunctionYesYesYes (as undefined)
letBlockYesNoYes (TDZ)
constBlockNoNoYes (TDZ)

What Interviewers Actually Check

  • Whether you know var is function-scoped and let/const are block-scoped
  • Whether you can explain the loop closure bug and why let fixes it
  • Whether you understand that const prevents reassignment but not mutation
  • Whether you know the temporal dead zone applies to let and const but not var
  • Whether you can state a clear default recommendation and explain when to switch from const to let

Follow-Up Questions

  1. If you need a truly immutable object in JavaScript, what must you do beyond using const?
  2. How does TypeScript's readonly keyword relate to what const does for objects?
  3. In a module that exports a const object, can the importing module mutate its properties?
  4. Why was var not simply removed from the language when let and const were introduced?
  5. A senior engineer proposes a lint rule that forbids all uses of let and requires const everywhere. What would break, and is it a good rule?

Common Candidate Mistakes

  • Thinking const makes an object immutable when it only prevents reassignment of the binding
  • Using var inside a loop and being surprised that all closures share the same variable
  • Trying to re-declare a let variable in the same scope and not expecting a SyntaxError
  • Confusing the hoisting behavior of var (initialized to undefined) with let and const (TDZ)
  • Using var in 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 var and let/const with a block-level example?
  • Can you explain why const does not make an object immutable and demonstrate it with code?
  • Can you explain the classic loop closure bug with var and how let fixes it?
  • Can you state the default recommendation and justify when let is needed instead of const?
  • Can you name the hoisting behavior difference between var and let/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.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

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