Full Stack • Java • System Design • Cloud • AI Engineering

JavaScript2026-06-06

JavaScript Variables: Complete Guide to var, let, and const

Master JavaScript variables: var vs let vs const, scope, hoisting, temporal dead zone, memory allocation, and best practices with detailed examples and diagrams.

JavaScript Variables: Complete Guide to var, let, and const


📋 Table of Contents

  1. What are JavaScript Variables?
  2. Why Do We Need Variables?
  3. Real-World Use Cases
  4. Syntax
  5. Internal Working
  6. Memory Diagram
  7. Data Flow Diagram
  8. Code Examples
  9. Common Mistakes
  10. Performance Considerations
  11. Interview Questions
  12. Cheat Sheet
  13. Summary

1. What are JavaScript Variables?

Variables are named containers that store data values. They act as references to memory locations where data is stored.

Variable Declaration Keywords

┌─────────────────────────────────────────────────────────────┐
│         JAVASCRIPT VARIABLE KEYWORDS                         │
└─────────────────────────────────────────────────────────────┘

JavaScript Variable Declarations
         |
    ┌────┴────────────────┐
    |                     |
    ├─ var (ES5 - 1997)
    |  • Function scope
    |  • Hoisted with undefined
    |  • Can redeclare
    |  • Can reassign
    |
    ├─ let (ES6 - 2015)
    |  • Block scope
    |  • Hoisted in TDZ
    |  • Cannot redeclare
    |  • Can reassign
    |
    └─ const (ES6 - 2015)
       • Block scope
       • Hoisted in TDZ
       • Cannot redeclare
       • Cannot reassign
       • Must initialize

Variable Lifecycle

┌─────────────────────────────────────────────────────────────┐
│         VARIABLE LIFECYCLE                                   │
└─────────────────────────────────────────────────────────────┘

1. Declaration
   ↓
   let age;
   
2. Initialization
   ↓
   age = 30;
   
3. Usage
   ↓
   console.log(age);
   
4. Reassignment (if mutable)
   ↓
   age = 31;
   
5. Garbage Collection
   ↓
   (when out of scope)

2. Why Do We Need Variables?

Variables provide essential benefits for programming.

Without Variables (Repetitive)

console.log("John Doe");
console.log("John Doe");
console.log("John Doe");
// Hard to maintain, error-prone

With Variables (Maintainable)

const name = "John Doe";
console.log(name);
console.log(name);
console.log(name);
// Easy to maintain, single source of truth

Benefits

┌─────────────────────────────────────────────────────────────┐
│         BENEFITS OF VARIABLES                                │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ✓ Reusability                                               │
│    • Use same value multiple times                          │
│    • Reduce code duplication                                │
│                                                              │
│  ✓ Readability                                               │
│    • Meaningful names improve understanding                 │
│    • Self-documenting code                                  │
│                                                              │
│  ✓ Maintainability                                           │
│    • Change value in one place                              │
│    • Easier to update and debug                             │
│                                                              │
│  ✓ Memory Management                                         │
│    • Efficient data storage                                 │
│    • Automatic garbage collection                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

3. Real-World Use Cases

1. Configuration Management

// API configuration
const API_BASE_URL = "https://api.example.com";
const API_TIMEOUT = 5000;
const API_VERSION = "v1";

// Easy to update in one place
const endpoint = `${API_BASE_URL}/${API_VERSION}/users`;

2. User State Management

let isLoggedIn = false;
let currentUser = null;
let sessionToken = "";

function login(user, token) {
    isLoggedIn = true;
    currentUser = user;
    sessionToken = token;
}

3. Loop Counters and Iterators

// Block-scoped loop variable
for (let i = 0; i < 10; i++) {
    console.log(i);
}
// i is not accessible here

// Accumulator
let total = 0;
for (const item of items) {
    total += item.price;
}

4. Syntax

var Declaration

// Declaration
var name;

// Declaration with initialization
var age = 30;

// Multiple declarations
var x = 1, y = 2, z = 3;

// Redeclaration (allowed)
var age = 30;
var age = 31;  // No error

// Reassignment (allowed)
var city = "New York";
city = "Los Angeles";  // OK

let Declaration

// Declaration
let name;

// Declaration with initialization
let age = 30;

// Multiple declarations
let x = 1, y = 2, z = 3;

// Redeclaration (NOT allowed)
let age = 30;
// let age = 31;  // SyntaxError

// Reassignment (allowed)
let city = "New York";
city = "Los Angeles";  // OK

const Declaration

// Declaration with initialization (required)
const PI = 3.14159;

// Declaration without initialization (ERROR)
// const TAX_RATE;  // SyntaxError: Missing initializer

// Redeclaration (NOT allowed)
const MAX_SIZE = 100;
// const MAX_SIZE = 200;  // SyntaxError

// Reassignment (NOT allowed)
const API_KEY = "abc123";
// API_KEY = "xyz789";  // TypeError

// Object properties CAN be modified
const user = { name: "John" };
user.name = "Jane";  // OK (reference unchanged)
user.age = 30;       // OK (adding property)

// Array elements CAN be modified
const numbers = [1, 2, 3];
numbers.push(4);     // OK (reference unchanged)
numbers[0] = 10;     // OK (modifying element)

// But reassignment is NOT allowed
// user = {};         // TypeError
// numbers = [];      // TypeError

5. Internal Working

Scope Comparison

┌─────────────────────────────────────────────────────────────┐
│         SCOPE DIFFERENCES                                    │
└─────────────────────────────────────────────────────────────┘

var (Function Scope)
────────────────────
function example() {
    if (true) {
        var x = 10;
    }
    console.log(x);  // 10 (accessible!)
}

Scope Diagram:
┌─────────────────────┐
│  Function Scope     │
│  ┌───────────────┐  │
│  │  if Block     │  │
│  │  var x = 10   │  │
│  └───────────────┘  │
│  console.log(x) ✓   │  ← var accessible
└─────────────────────┘

let/const (Block Scope)
───────────────────────
function example() {
    if (true) {
        let y = 10;
    }
    console.log(y);  // ReferenceError
}

Scope Diagram:
┌─────────────────────┐
│  Function Scope     │
│  ┌───────────────┐  │
│  │  if Block     │  │
│  │  let y = 10   │  │
│  └───────────────┘  │
│  console.log(y) ✗   │  ← let not accessible
└─────────────────────┘

Hoisting Behavior

┌─────────────────────────────────────────────────────────────┐
│         HOISTING MECHANISM                                   │
└─────────────────────────────────────────────────────────────┘

var Hoisting
────────────
Code Written:
console.log(x);  // undefined
var x = 10;

How JavaScript Sees It:
var x;           // Declaration hoisted
console.log(x);  // undefined
x = 10;          // Assignment stays

let/const Hoisting
──────────────────
Code Written:
console.log(y);  // ReferenceError
let y = 10;

How JavaScript Sees It:
// y is hoisted but in TDZ
console.log(y);  // ReferenceError: Cannot access before initialization
let y = 10;

Temporal Dead Zone (TDZ):
┌─────────────────────────┐
│  Scope Start            │
│  ↓                      │
│  [TDZ for y]            │  ← Cannot access
│  ↓                      │
│  let y = 10;            │  ← TDZ ends
│  ↓                      │
│  console.log(y); // 10  │  ← Can access
└─────────────────────────┘

Temporal Dead Zone (TDZ)

// TDZ Example
{
    // TDZ starts for 'x'
    console.log(x);  // ReferenceError
    
    let x = 10;      // TDZ ends
    
    console.log(x);  // 10 (accessible)
}

// TDZ with typeof
console.log(typeof undeclaredVar);  // "undefined" (no error)
console.log(typeof declaredVar);    // ReferenceError (in TDZ)
let declaredVar = 10;

6. Memory Diagram

┌─────────────────────────────────────────────────────────────┐
│         MEMORY ALLOCATION                                    │
└─────────────────────────────────────────────────────────────┘

Code:
─────
var a = 10;
let b = 20;
const c = 30;
const obj = { x: 1 };

Memory Layout:
──────────────

┌──────────────────────────────────────┐
│  GLOBAL SCOPE                         │
├──────────────────────────────────────┤
│  var a: 10                            │  ← Global object property
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│  SCRIPT SCOPE (let/const)             │
├──────────────────────────────────────┤
│  let b: 20                            │  ← Separate scope
│  const c: 30                          │  ← Separate scope
│  const obj: 0x1000 ──────────┐       │  ← Reference
└──────────────────────────────│───────┘
                               │
                               v
┌──────────────────────────────────────┐
│  HEAP MEMORY                          │
├──────────────────────────────────────┤
│  0x1000: { x: 1 }                    │  ← Object data
└──────────────────────────────────────┘

const with Objects:
───────────────────
const user = { name: "John" };

Stack:
┌──────────────────┐
│ user: 0x2000     │  ← Reference (immutable)
└──────────────────┘
         |
         v
Heap:
┌──────────────────┐
│ 0x2000:          │
│ { name: "John" } │  ← Object (mutable)
└──────────────────┘

user.name = "Jane";  // OK (modifying object)
// user = {};        // Error (changing reference)

7. Data Flow Diagram

┌─────────────────────────────────────────────────────────────┐
│         VARIABLE DECLARATION FLOW                            │
└─────────────────────────────────────────────────────────────┘

var Declaration Flow:
─────────────────────
Source Code
    ↓
Parse Phase
    ↓
Hoist Declaration
    ↓
Initialize with undefined
    ↓
Execution Phase
    ↓
Assignment
    ↓
Value Available

let/const Declaration Flow:
───────────────────────────
Source Code
    ↓
Parse Phase
    ↓
Hoist Declaration
    ↓
Place in TDZ
    ↓
Execution Phase
    ↓
Reach Declaration Line
    ↓
Initialize (TDZ ends)
    ↓
Value Available

Block Scope Resolution:
───────────────────────
Variable Access Request
    ↓
Check Current Block
    ↓
Found? → Return Value
    ↓ Not Found
Check Parent Block
    ↓
Found? → Return Value
    ↓ Not Found
Check Global Scope
    ↓
Found? → Return Value
    ↓ Not Found
ReferenceError

8. Code Examples

Example 1: Scope Differences

// var: Function scope
function varExample() {
    if (true) {
        var x = 10;
    }
    console.log(x);  // 10 (accessible)
}

// let: Block scope
function letExample() {
    if (true) {
        let y = 10;
    }
    // console.log(y);  // ReferenceError
}

// Loop with var (problem)
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Output: 3, 3, 3 (all reference same i)

// Loop with let (correct)
for (let j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100);
}
// Output: 0, 1, 2 (each iteration has own j)

Example 2: Hoisting Behavior

// var hoisting
console.log(a);  // undefined (not error)
var a = 10;
console.log(a);  // 10

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

// const hoisting (TDZ)
// console.log(c);  // ReferenceError
const c = 30;
console.log(c);  // 30

// Function hoisting
greet();  // "Hello!" (works)
function greet() {
    console.log("Hello!");
}

Example 3: const with Objects and Arrays

// const with objects
const user = {
    name: "John",
    age: 30
};

// Modifying properties (OK)
user.name = "Jane";
user.age = 31;
user.email = "[email protected]";
console.log(user);  // { name: "Jane", age: 31, email: "[email protected]" }

// Reassigning (ERROR)
// user = { name: "Bob" };  // TypeError

// const with arrays
const numbers = [1, 2, 3];

// Modifying array (OK)
numbers.push(4);
numbers[0] = 10;
console.log(numbers);  // [10, 2, 3, 4]

// Reassigning (ERROR)
// numbers = [5, 6, 7];  // TypeError

// Freezing objects (truly immutable)
const config = Object.freeze({
    apiKey: "abc123",
    timeout: 5000
});

// config.apiKey = "xyz789";  // Silently fails (strict mode: TypeError)
console.log(config.apiKey);  // "abc123" (unchanged)

Example 4: Temporal Dead Zone

// TDZ example
function tdz Example() {
    // TDZ starts for 'x'
    
    console.log(typeof x);  // ReferenceError (in TDZ)
    
    let x = 10;  // TDZ ends here
    
    console.log(x);  // 10
}

// TDZ with default parameters
function greet(name = getName()) {
    // getName is called before 'name' is initialized
    function getName() {
        return "Guest";
    }
    return `Hello, ${name}`;
}

console.log(greet());  // "Hello, Guest"

Example 5: Best Practices

// ✅ Good: Use const by default
const MAX_RETRIES = 3;
const API_URL = "https://api.example.com";

// ✅ Good: Use let when value changes
let counter = 0;
let isLoading = false;

function incrementCounter() {
    counter++;
}

// ❌ Bad: Using var
var globalVar = "avoid this";

// ✅ Good: Meaningful names
const userAge = 30;
const isAuthenticated = true;

// ❌ Bad: Unclear names
const x = 30;
const flag = true;

// ✅ Good: Block scope for temporary variables
{
    const tempData = processData();
    saveToDatabase(tempData);
    // tempData not accessible outside block
}

// ✅ Good: Const for objects that won't be reassigned
const config = {
    theme: "dark",
    language: "en"
};
config.theme = "light";  // OK to modify properties

9. Common Mistakes

Mistake 1: Using var in Loops

// ❌ Wrong: var in loop
for (var i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i);  // 3, 3, 3
    }, 100);
}

// ✅ Correct: let in loop
for (let i = 0; i < 3; i++) {
    setTimeout(() => {
        console.log(i);  // 0, 1, 2
    }, 100);
}

Mistake 2: Accessing Before Declaration

// ❌ Wrong: Accessing let/const before declaration
console.log(name);  // ReferenceError
let name = "John";

// ✅ Correct: Declare before use
let name = "John";
console.log(name);  // "John"

Mistake 3: Trying to Reassign const

// ❌ Wrong: Reassigning const
const PI = 3.14;
PI = 3.14159;  // TypeError

// ✅ Correct: Use let if value changes
let approximatePi = 3.14;
approximatePi = 3.14159;  // OK

Mistake 4: Forgetting to Initialize const

// ❌ Wrong: const without initialization
const API_KEY;  // SyntaxError
API_KEY = "abc123";

// ✅ Correct: Initialize immediately
const API_KEY = "abc123";

Mistake 5: Assuming const Makes Objects Immutable

// ❌ Wrong assumption: const object is immutable
const user = { name: "John" };
user.name = "Jane";  // This works!
console.log(user.name);  // "Jane"

// ✅ Correct: Use Object.freeze for immutability
const user = Object.freeze({ name: "John" });
user.name = "Jane";  // Silently fails (strict mode: error)
console.log(user.name);  // "John"

10. Performance Considerations

1. Block Scope vs Function Scope

// ✅ Better: Block scope (let/const)
// Variables are garbage collected when block ends
{
    let largeArray = new Array(1000000);
    // Use largeArray
}  // largeArray is eligible for GC

// ❌ Slower: Function scope (var)
// Variables live until function ends
function process() {
    if (condition) {
        var largeArray = new Array(1000000);
        // Use largeArray
    }
    // largeArray still in memory!
}

2. Const Optimization

// ✅ Fast: Engine can optimize const
const PI = 3.14159;
const result = PI * radius * radius;
// Engine knows PI never changes

// ❌ Slower: Engine must check for changes
let pi = 3.14159;
const result = pi * radius * radius;
// Engine must account for potential changes

3. Avoid Global Variables

// ❌ Slow: Global lookup
var globalCounter = 0;
function increment() {
    globalCounter++;  // Slower lookup
}

// ✅ Fast: Local scope
function createCounter() {
    let counter = 0;
    return () => counter++;  // Faster lookup
}

11. Interview Questions

Q1: What's the difference between var, let, and const?

Answer:

Feature var let const
Scope Function Block Block
Hoisting Yes (undefined) Yes (TDZ) Yes (TDZ)
Redeclaration Yes No No
Reassignment Yes Yes No
Initialization Optional Optional Required
Introduced ES5 (1997) ES6 (2015) ES6 (2015)

Q2: What is the Temporal Dead Zone (TDZ)?

Answer: The TDZ is the period between entering a scope and the actual declaration of a let/const variable. During this time, the variable exists but cannot be accessed.

{
    // TDZ starts
    console.log(x);  // ReferenceError
    let x = 10;      // TDZ ends
    console.log(x);  // 10
}

Q3: Are let and const hoisted?

Answer: Yes, both let and const are hoisted, but unlike var, they are not initialized with undefined. They remain in the TDZ until their declaration is reached, making them inaccessible before declaration.

Q4: Can you modify a const object?

Answer: Yes, you can modify properties of a const object because const only prevents reassignment of the variable itself, not mutation of the object it references.

const user = { name: "John" };
user.name = "Jane";  // OK (modifying property)
// user = {};        // Error (reassigning variable)

Q5: Why is var considered problematic?

Answer: var has several issues:

  • Function scope (not block scope) leads to unexpected behavior
  • Allows redeclaration, causing bugs
  • Hoisting with undefined can cause confusion
  • Creates properties on global object
  • No temporal dead zone protection

Q6: What happens in this code?

for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}

Answer: It prints "3, 3, 3" because var has function scope. All setTimeout callbacks reference the same i variable, which is 3 after the loop completes. Using let would print "0, 1, 2" because each iteration gets its own i.


12. Cheat Sheet

// ═══════════════════════════════════════════════════════════
// VARIABLES QUICK REFERENCE
// ═══════════════════════════════════════════════════════════

// VAR (Avoid)
// ───────────
var x = 10;              // Function scope
var x = 20;              // Redeclaration OK
x = 30;                  // Reassignment OK
console.log(y);          // undefined (hoisted)
var y = 40;

// LET (Use for mutable values)
// ────────────────────────────
let a = 10;              // Block scope
// let a = 20;           // Error: Cannot redeclare
a = 30;                  // Reassignment OK
// console.log(b);       // Error: TDZ
let b = 40;

// CONST (Use by default)
// ──────────────────────
const PI = 3.14;         // Block scope
// const TAX;            // Error: Must initialize
// const PI = 3.14159;   // Error: Cannot redeclare
// PI = 3.14159;         // Error: Cannot reassign

// CONST with Objects/Arrays
// ─────────────────────────
const obj = { x: 1 };
obj.x = 2;               // OK (modify property)
obj.y = 3;               // OK (add property)
// obj = {};             // Error (reassign)

const arr = [1, 2];
arr.push(3);             // OK (modify array)
arr[0] = 10;             // OK (modify element)
// arr = [];             // Error (reassign)

// SCOPE EXAMPLES
// ──────────────
// Block scope (let/const)
{
    let x = 10;
    const y = 20;
}
// console.log(x);       // Error: Not accessible

// Function scope (var)
function test() {
    if (true) {
        var z = 30;
    }
    console.log(z);      // 30 (accessible)
}

// BEST PRACTICES
// ──────────────
✓ Use const by default
✓ Use let when value needs to change
✓ Never use var
✓ Declare variables at the top of their scope
✓ Use meaningful names
✓ One variable per line
✗ Don't use var
✗ Don't access before declaration
✗ Don't assume const makes objects immutable

13. Summary

Key Takeaways

const is the default choice for variables
let for values that need to change
var should be avoided in modern JavaScript
Block scope (let/const) is safer than function scope (var)
TDZ protects against accessing uninitialized variables
Hoisting occurs for all declarations
const prevents reassignment, not mutation
let/const don't create global object properties
Scope determines variable accessibility
Memory is managed automatically

Best Practices

  1. Use const by default
  2. Use let only when reassignment is needed
  3. Never use var in modern code
  4. Declare variables at the top of their scope
  5. Use meaningful, descriptive names
  6. Avoid global variables
  7. Use block scope to limit variable lifetime
  8. Initialize variables when declaring
  9. Use Object.freeze() for true immutability
  10. Understand hoisting and TDZ

Understanding JavaScript variables is fundamental to writing clean, maintainable, and bug-free code.