JavaScript Variables

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.

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
Common Mistakes:
  • Using var in loops (creates bugs due to function scope). Prefer let.
  • Expecting const to freeze objects (it only locks the binding). Use Object.freeze() for shallow immutability.
  • Accessing let/const before declaration (TDZ → ReferenceError).
  • Accidentally creating globals (assignment without declaration in non-strict code).
Best Practices: Use 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.