Skip to main content

Scope of var, let and const

var is function scoped while let and const are block scoped, so a variable declared with var inside an if or a loop leaks into the whole function, whereas let and const stay inside their own curly braces. Besides scope, the three differ in how they behave during hoisting, whether they can be redeclared, and whether they can be reassigned.

Theory

TL;DR

  • var has function scope, let and const have block scope.
  • All three are hoisted, but var is initialised to undefined, while let and const sit in the TDZ until their declaration line and throw a ReferenceError.
  • Redeclaration in the same scope: var allows it, let and const throw a SyntaxError.
  • Reassignment: var and let allow it, const forbids it, but mutating the object behind the reference is still allowed.
  • Modern practice: const by default, let when needed, var only in legacy code.

Quick example

javascript
function test() { if (true) { var x = 1; let y = 2; const z = 3; } console.log(x); // 1 console.log(y); // ReferenceError console.log(z); // ReferenceError } test();

x is available because var "ignores" the if block and is hoisted to the function level. y and z are visible only inside the if.

Scope: function versus block

KeywordScope
varFunction scope
let, constBlock scope

Function scope means the variable is visible everywhere in the function where it is declared, regardless of any {} block.

Block scope means the variable is visible only inside the block { ... } where it is declared: the body of an if, the body of a loop, any pair of curly braces.

Hoisting and the TDZ

All variables are hoisted to the top of their scope, but they behave differently.

KeywordHoistedUsable before the declaration?
varyesyes, but the value is undefined
let, constyesno, an error because of the TDZ (temporal dead zone)

Example with var:

javascript
console.log(a); // undefined var a = 10;

JavaScript hoists the declaration var a, but not the assignment a = 10.

Example with let and const:

javascript
console.log(b); // ReferenceError let b = 20;

b is hoisted too, but it lives in the temporal dead zone (TDZ): from the start of the scope up to the actual declaration line. Any access in that window throws a ReferenceError.

Redeclaration and reassignment

KeywordCan it be redeclared?
varYes, it overwrites the value
let, constNo, SyntaxError
javascript
var x = 1; var x = 2; // allowed console.log(x); // 2 let y = 1; let y = 2; // SyntaxError
KeywordCan the value be changed?
varYes
letYes
constNo, but the object inside can be mutated
javascript
let name = 'Maria'; name = 'Oleh'; // allowed const user = { name: 'Maria' }; user.name = 'Oleh'; // allowed: we mutate the object user = {}; // TypeError: the reference cannot be reassigned

const freezes the binding, that is the reference, not the contents of the object. To freeze the contents as well you need Object.freeze.

An example that shows every difference

javascript
function demo() { console.log(a); // undefined // console.log(b); // ReferenceError // console.log(c); // ReferenceError var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // the same variable! let b = 20; // a new variable const c = 30; // a new variable console.log(a, b, c); // 10 20 30 } console.log(a, b, c); // 10 2 3 } demo();

var a leaked out of the block and overwrote the outer value, while let and const created their own local variables inside the block, so the outer b and c were left untouched.

Which one to use in practice

KeywordWhen to use it
letFor variables whose value has to change
constBy default, always, unless the reference has to change
varNever, only to maintain legacy code

Summary table:

Propertyvarletconst
ScopeFunctionBlockBlock
HoistedYesYesYes
Use before declarationundefinedError (TDZ)Error (TDZ)
RedeclarationYesNoNo
ReassignmentYesYesNo
Modern standardLegacyRecommendedDefault

Common mistakes

  • Assuming that a var inside an if or a for creates a local variable. It actually belongs to the whole function and can overwrite the outer value.
  • Confusing hoisting with "no error". let and const are hoisted too, accessing them inside the TDZ simply fails with a ReferenceError.
  • Thinking const makes the value immutable. It only forbids reassigning the variable; object fields and array elements can still be mutated.
  • Using var in a loop together with asynchronous code: every iteration sees one shared variable, whereas let creates a fresh binding per iteration.
  • Relying on var redeclaration as a way to "refresh" a variable: it is a silent overwrite that hides bugs.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.