Variables store data in memory. JavaScript lets you declare with var, let, and const.
The choice affects scope, reassignment, and hoisting. This page explains all three in detail with practical examples.
JavaScript Variables
Quick Comparison: var vs let vs const
| Keyword | Scope | Reassign? | Redeclare? | Hoisting | Typical Use |
|---|---|---|---|---|---|
| var | Function-scoped (or global) | Yes | Yes (same scope) | Hoisted and initialized to undefined |
Legacy code; avoid in modern JS |
| let | Block-scoped ({}) |
Yes | No (same scope) | Hoisted but in TDZ (not initialized) | Mutable bindings inside blocks & loops |
| const | Block-scoped ({}) |
No (binding is constant) | No (same scope) | Hoisted but in TDZ (not initialized) | Prefer by default; mutate internal object/array if needed |
Note: “TDZ” = Temporal Dead Zone, the time from start of scope until the declaration line executes.
1) Declaring Variables
Use let for values that will change, const when they won’t; avoid var in new code.
let count = 1;
const SITE = 'codingwithsonu.com';
var legacy = 10; // function-scoped
2) Scope: Global, Function, and Block
let/const are block-scoped; var ignores blocks and is function- or global-scoped.
function demo() {
if (true) {
let x = 1;
var y = 2;
}
// x is not accessible here
console.log(y); // 2 (var escapes the block)
}
demo();
3) Hoisting
var is hoisted and initialized to undefined; let/const are hoisted but not initialized (TDZ).
console.log(a); // undefined (var hoisted)
var a = 5;
// console.log(b); // ReferenceError (TDZ)
let b = 7;
4) Temporal Dead Zone (TDZ)
Accessing a let/const binding before its declaration throws a ReferenceError.
function tdz() {
// console.log(msg); // ReferenceError
const msg = 'ready';
return msg;
}
5) Reassignment vs Mutation
const prevents rebinding, not mutation of objects/arrays.
const user = { name: 'Asha' };
// user = {}; // ❌ TypeError (rebind)
user.name = 'Sonu'; // ✅ mutate ok
const arr = [1,2];
arr.push(3); // [1,2,3]
6) Shadowing & Redeclaration
Inner scopes can shadow outer variables. Redeclaration rules differ between var and let/const.
let n = 1;
{
let n = 2; // shadows outer n
console.log(n); // 2
}
console.log(n); // 1
var x = 1;
var x = 2; // allowed
// let x = 3; // ❌ SyntaxError in same scope
7) Globals, Strict Mode & Modules
Accidentally assigning to an undeclared identifier creates a global (non-strict). In modules/strict mode this throws.
// Non-strict (legacy):
function old() {
// oopsGlobal = 123; // creates window.oopsGlobal
}
// Strict / modules:
'use strict';
// oops = 1; // ❌ ReferenceError
// Access the global object portably:
console.log(globalThis === window); // true (in browsers)
8) Destructuring Declarations
Unpack arrays/objects directly into variables; defaults fill missing values.
// array
const [a, b = 0] = [1];
// a = 1, b = 0
// object
const { name, age: years = 18 } =
{ name: 'Sonu' };
// name="Sonu", years=18
9) Naming Rules & Conventions
Identifiers may contain letters, digits (not first), _, $; case-sensitive; reserved words are not allowed.
let userName = 'Asha'; // camelCase
const MAX_ITEMS = 10; // UPPER_SNAKE for constants
// let class = 1; // ❌ reserved word
10) Closures (Bonus)
Inner functions “remember” variables from their outer scope even after the outer function returns.
function makeCounter() {
let n = 0;
return function() {
return ++n;
};
}
const inc = makeCounter();
console.log(inc()); // 1
console.log(inc()); // 2
- Using
varin loops (creates bugs due to function scope). Preferlet. - Expecting
constto freeze objects (it only locks the binding). UseObject.freeze()for shallow immutability. - Accessing
let/constbefore declaration (TDZ → ReferenceError). - Accidentally creating globals (assignment without declaration in non-strict code).
const by default, switch to let when you need reassignment, and avoid var. Keep scopes small and prefer block-scoped variables inside loops/if blocks.