Blog / Coding tips

var vs let vs const in JavaScript: Differences, Hoisting and When to Use Each (Tested Examples)

Cover image for var vs let vs const in JavaScript: Differences, Hoisting and When to Use Each (Tested Examples)

The short answer: use const by default, use let when you need to reassign a variable, and avoid var in modern JavaScript. var is function-scoped, can be redeclared and is hoisted with the value undefined, which causes subtle bugs. let and const are block-scoped (they only exist inside the nearest { }), can’t be redeclared in the same scope, and throw a ReferenceError if you use them before the line that declares them. The only difference between let and const is that a const variable can’t be reassigned; the object or array it holds can still be changed.

Below, every difference is shown with a tested example. I ran all of them on Node.js 26.11.1 and pasted the real output, including the exact error messages, because those are often what people search for.

var vs let vs const at a glance

  • Scope: var is function-scoped; let and const are block-scoped.
  • Hoisting: all three are hoisted, but var starts as undefined, while let and const are in the “temporal dead zone” until declared.
  • Reassignment: var and let can be reassigned; const can’t.
  • Redeclaration: var can be redeclared in the same scope; let and const can’t.
  • Initial value: const must be given a value when declared; var and let default to undefined.
  • Global object: top-level var in a classic script creates a property on window/globalThis; let and const don’t.
  • Introduced: var has existed since the start; let and const arrived in ES2015 (ES6).

Scope: function scope vs block scope

A block is any code between curly braces: an if, a loop, or just { }. let and const live only inside their block. var ignores blocks and belongs to the whole function (or the whole script, if it’s not in a function).

if (true) { var a = 1; let b = 2; const c = 3; }
console.log(typeof a, typeof b, typeof c);
number undefined undefined

a leaked out of the if block; b and c didn’t. Leaking is rarely what you want. It’s how a variable meant for one small piece of code ends up overwriting another with the same name.

Shadowing with let

Because let is block-scoped, an inner block can have its own variable with the same name without affecting the outer one:

let s = "outer";
{ let s = "inner"; console.log(s); }
console.log(s);
inner
outer

Hoisting and the temporal dead zone

Hoisting means JavaScript registers declarations before running the code in a scope. All three keywords are hoisted, but they behave very differently if you use the variable too early.

const r1 = x; var x = 5;    // undefined
const r2 = y; let y = 5;    // error
const r3 = z; const z = 5;  // error
var before -> undefined
let before -> ReferenceError: Cannot access 'y' before initialization
const before -> ReferenceError: Cannot access 'z' before initialization

With var, the variable exists from the start of the function with the value undefined, so the bug goes unnoticed and you silently get undefined. With let and const, the time between the start of the block and the declaration line is called the temporal dead zone (TDZ). Touching the variable there throws immediately, which surfaces the mistake.

“Cannot access before initialization”

That error message means you used a let, const or class before its declaration ran. Fix: move the declaration above the code that uses it, or check for a circular import between modules.

typeof and the temporal dead zone

typeof on a variable that was never declared returns "undefined" safely. In the TDZ, even typeof throws:

typeof undeclared -> undefined
typeof TDZ -> ReferenceError: Cannot access 'w' before initialization

Reassignment: let vs const

let allows reassignment; const doesn’t:

let n = 1; n = 2;            // fine
const m = 1; m = 2;          // error
let reassign -> 2
const reassign -> TypeError: Assignment to constant variable.

“Assignment to constant variable”

This TypeError means you tried to give a const a new value. Fix: if the value really needs to change, declare it with let. A common case is a loop counter, covered below.

Is const immutable? (No: objects and arrays can change)

const stops the variable from pointing at something else. It doesn’t freeze the value. Objects and arrays declared with const can still be modified:

const user = { name: "Mahir" };
user.name = "M. Faysal";
console.log(user);

const list = [1, 2];
list.push(3);
console.log(list);
{ name: 'M. Faysal' }
[ 1, 2, 3 ]

What you can’t do is user = {} or list = []. If you need an object that can’t be changed, use Object.freeze():

const frozen = Object.freeze({ n: 1 });
frozen.n = 2;
console.log(frozen);
{ n: 1 }

The assignment was silently ignored (in strict mode it throws a TypeError). Object.freeze() is shallow: nested objects can still change. To copy objects safely instead of changing them, see my guide to deep copying an object in JavaScript.

Redeclaration: “Identifier has already been declared”

var lets you declare the same name twice, which can quietly overwrite a value:

var r1 = 1;
var r1 = 2;
console.log(r1);
2

let and const refuse:

let redeclare -> SyntaxError: Identifier 'q' has already been declared

That SyntaxError is a gift: it catches accidental name clashes before the code runs. Fix: rename one variable, or remove the second let and just assign (q = 2).

const must be initialised

A const has to get its value on the same line, because you can never assign it later:

const no init -> SyntaxError: Missing initializer in const declaration
let no init -> undefined

let k; is fine and starts as undefined. const k; is a syntax error.

The classic for-loop closure bug

This is the example that convinced many developers to switch from var. Each function below should remember its own loop number:

const fv = [];
for (var i = 0; i < 3; i++) fv.push(() => i);
console.log(fv.map(f => f()));

const fl = [];
for (let j = 0; j < 3; j++) fl.push(() => j);
console.log(fl.map(f => f()));
[ 3, 3, 3 ]
[ 0, 1, 2 ]

With var, there’s only one i for the whole function, so every function sees its final value, 3. With let, each loop iteration gets a new j, so each function keeps its own number. The same bug appears with setTimeout in a loop and with event listeners, and let fixes it.

The same bug with setTimeout

The most common real-world version uses setTimeout. I pushed each loop value into an array after a zero-millisecond delay:

const r = [];
for (let i = 0; i < 3; i++) setTimeout(() => r.push(i), 0);
const v = [];
for (var k = 0; k < 3; k++) setTimeout(() => v.push(k), 0);
setTimeout(() => console.log(r, v), 5);
[ 0, 1, 2 ] [ 3, 3, 3 ]

By the time the timers run, the var loop has finished and k is 3 for every callback. If you’re writing timers or delayed callbacks, my guide to debouncing a function relies on this behaviour.

const in loops

const works in for...of and for...in loops, because each iteration creates a fresh binding:

for (const item of ["a", "b"]) console.log(item);
a
b

But not in a classic counting loop, where i++ reassigns the variable:

for const i++ -> TypeError: Assignment to constant variable.

Rule of thumb: const for for...of, let for for (let i = 0; ...).

Global variables and the window object

In a classic browser script (not a module), a top-level var becomes a property of the global object, window (or globalThis); let and const don’t. I ran this as a classic script in Node:

var gv = 1; let gl = 2;
console.log(globalThis.gv, globalThis.gl);
1 undefined

That means a top-level var can collide with built-in browser globals or other scripts. Note that inside ES modules (type="module" scripts, .mjs files) and CommonJS files, top-level variables are scoped to the module, so even var doesn’t become global; when I ran the same lines inside a CommonJS file, both printed undefined.

Forgetting the keyword entirely

If you assign to a name with no var, let or const, sloppy-mode JavaScript creates a global by accident. Strict mode, which ES modules and classes use automatically, throws instead:

sloppy implicit global -> 5
strict implicit -> ReferenceError: implicitH is not defined

Always declare variables, and prefer modules or "use strict" so mistakes are caught.

When to use const vs let (and should you ever use var?)

Most style guides and linters today recommend:

  1. Use const by default. Most variables are never reassigned, and const makes that clear to readers.
  2. Use let when the variable must be reassigned: counters, accumulators, values that change in a loop or if.
  3. Avoid var in new code. Its function scope, hoisting to undefined and redeclaration rules cause bugs that let and const prevent.

ESLint has rules for both habits: prefer-const flags let variables that are never reassigned, and no-var flags var.

When you might still see var

  • Old code and tutorials written before 2015.
  • Code that must run in very old browsers such as Internet Explorer 10, which don’t support let and const. Every current browser and Node.js version supports them, so this rarely applies today.
  • Build tool output, which sometimes uses var after transpiling. You don’t edit that by hand.

let and const in switch statements

All the case clauses of a switch share one block, so declaring the same let name in two cases fails:

switch (1) { case 1: let m = 1; break; case 2: let m = 2; }
SyntaxError: Identifier 'm' has already been declared

Fix: wrap each case in its own braces so each gets its own block:

function f(x) {
  switch (x) {
    case 1: { let msg = "one"; return msg; }
    case 2: { let msg = "two"; return msg; }
  }
}
console.log(f(2));
two

const with destructuring

const works with destructuring, which unpacks values from objects and arrays into separate variables in one line:

const { name, roles: [first] } = { name: "Mahir", roles: ["admin", "editor"] };
console.log(name, first);
Mahir admin

This is very common when handling API responses, and async/await code is almost always written with const for the awaited result.

var, let and const interview questions

These come up often in junior developer interviews, and every answer is covered above:

  • What will for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)) print? (3, 3, 3)
  • Can you change a property of a const object? (Yes)
  • What is the temporal dead zone? (The time before a let/const declaration runs, when accessing it throws)
  • Are let and const hoisted? (Yes, but not initialised)
  • Why does var become a property of window? (Top-level var in a classic script creates a global object property)

A practical example: refactoring var to let and const

Here’s a typical old-style snippet:

var total = 0;
var prices = [10, 20, 30];
for (var i = 0; i < prices.length; i++) {
  var price = prices[i];
  total = total + price;
}

Refactored:

const prices = [10, 20, 30];
let total = 0;
for (const price of prices) {
  total += price;
}

prices never changes, so it’s const. total changes, so it’s let. The loop variable doesn’t leak out. You could go further with prices.reduce((sum, p) => sum + p, 0), which my guide to map, filter and reduce explains.

A quick decision guide

When you’re about to declare a variable, ask two questions:

  1. Will this name ever point to a different value? If no, use const. That covers most variables: imported modules, configuration, DOM elements you look up once, functions stored in variables, arrays and objects you only modify, and results of calculations.
  2. If yes, does it change inside a loop or condition? Use let. Typical examples are counters, running totals, a “found” flag, or a value you reassign in an if/else.

You should almost never need a third option. If you find yourself reaching for var to make a variable visible outside a block, declare it with let before the block instead:

let result;
if (condition) {
  result = "yes";
} else {
  result = "no";
}

Even better, when both branches just pick a value, use a conditional expression and keep const: const result = condition ? "yes" : "no";.

Does const make code faster?

You’ll sometimes read that const is faster than let. Modern engines optimise both very well, and any difference is negligible in everyday code. Choose const because it communicates intent and prevents accidental reassignment, not for speed.

Common errors and what they mean

  • ReferenceError: Cannot access 'x' before initialization: you used a let/const in its temporal dead zone. Move the declaration up.
  • TypeError: Assignment to constant variable.: you reassigned a const. Use let.
  • SyntaxError: Identifier 'x' has already been declared: you declared the same name twice with let/const in one scope. Rename or remove one declaration.
  • SyntaxError: Missing initializer in const declaration: you wrote const x; without a value.
  • ReferenceError: x is not defined: the variable doesn’t exist in this scope, often because a let/const was declared inside a block you’re now outside of.

FAQ

What is the difference between var, let and const?

var is function-scoped, can be redeclared and is hoisted as undefined. let and const are block-scoped, can’t be redeclared and throw if used before declaration. const also can’t be reassigned.

Should I use let or const?

Use const by default and switch to let only when the variable needs to be reassigned, such as a counter or a running total.

Is const immutable in JavaScript?

No. const prevents reassigning the variable, but the object or array it refers to can still be changed. Use Object.freeze() for a shallow read-only object.

Are let and const hoisted?

Yes, but they’re not initialised. Until the declaration line runs, they’re in the temporal dead zone, and accessing them throws ReferenceError: Cannot access 'x' before initialization.

Why should I not use var?

var ignores block scope, can be redeclared silently and is undefined before its declaration, which hides bugs such as the for-loop closure problem. let and const avoid all three.

Can I use const in a for loop?

Yes in for...of and for...in loops, because each iteration gets a new binding. No in for (const i = 0; i < n; i++), because i++ reassigns it and throws.

What is the temporal dead zone?

It’s the period between the start of a block and the line where a let or const variable is declared. Accessing the variable during that time throws a ReferenceError.

Does var create a global variable?

At the top level of a classic browser script, yes: it becomes a property of window. Inside a function, module or CommonJS file, it doesn’t.

// note

How to read this note.

This is a learning note from studying the web. It is one small topic, written so I can remember it. It is not a course and not a claim that I have finished the subject.

If a sentence is wrong, say so from the contact page and name this title. Drafts never appear here. Related notes, when they exist, are other published posts, and the same sample rule applies to each of them.

ModuleNotFoundError: No module named in Python: 10 Causes and Fixes (pip, venv, VS Code)
Coding tips

ModuleNotFoundError: No module named in Python: 10 Causes and Fixes (pip, venv, VS Code)

Python raises ModuleNotFoundError when it can’t find a module in the interpreter that’s running your code. The 10 causes I reproduced, from installing with the wrong pip to cv2/bs4/yaml package names, venvs, VS Code, Jupyter, local imports and externally-managed-environment, each with the exact fix.

October 12, 2026 · 13 min read · 2 views

What Is msconfig? Windows System Configuration Explained for Beginners
Coding tips

What Is msconfig? Windows System Configuration Explained for Beginners

msconfig opens Windows System Configuration, a built-in troubleshooting tool for controlling how Windows starts. What each tab does, how to do a clean boot, diagnostic startup and safe boot, how to escape a safe mode loop, and why it matters for security.

October 12, 2026 · 12 min read · 6 views