Docs

Module-02-Variables-and-Data-Types

2.1 Variables

šŸ“š Table of Contents

  1. •What Are Variables?
  2. •var - The Original Way
  3. •let - The Modern Way
  4. •const - Constants
  5. •Scope Differences
  6. •Temporal Dead Zone (TDZ)
  7. •Best Practices

What Are Variables?

A variable is a named container that stores a value in your program's memory. Think of it as a labeled box where you can put data.

// Creating a variable is like creating a labeled box
let userName = 'John'; // Box labeled "userName" contains "John"
let userAge = 25; // Box labeled "userAge" contains 25

Variable Operations:

OperationDescriptionExample
DeclarationCreating the variable namelet x;
InitializationGiving initial valuelet x = 5;
AssignmentChanging the valuex = 10;
ReadingGetting the valueconsole.log(x);

Declaration vs Initialization vs Assignment:

// Declaration only (no value yet)
let firstName;
console.log(firstName); // undefined

// Declaration with initialization
let lastName = 'Doe';
console.log(lastName); // "Doe"

// Assignment (changing value)
lastName = 'Smith';
console.log(lastName); // "Smith"

// Combined declaration and initialization
let age = 30;

var - The Original Way

var is the original way to declare variables in JavaScript (pre-ES6). It's still valid but has quirks that make it less predictable.

Basic Usage:

var name = 'John';
var age = 30;
var isActive = true;

console.log(name); // "John"
console.log(age); // 30
console.log(isActive); // true

Key Characteristics of var:

1. Function Scope (Not Block Scope)

var is scoped to the nearest function, not the nearest block:

function example() {
  if (true) {
    var x = 10; // Scoped to the function, not the if block
  }
  console.log(x); // 10 - x is accessible here!
}
example();

// Contrast with global scope:
if (true) {
  var y = 20; // Scoped to global (no function wrapper)
}
console.log(y); // 20 - y is global!

2. Hoisting (Declaration Moved to Top)

var declarations are "hoisted" to the top of their scope:

console.log(hoistedVar); // undefined (not an error!)
var hoistedVar = 'Hello';
console.log(hoistedVar); // "Hello"

// JavaScript interprets this as:
// var hoistedVar;              ← Declaration hoisted
// console.log(hoistedVar);     ← undefined
// hoistedVar = "Hello";        ← Assignment stays
// console.log(hoistedVar);     ← "Hello"

3. Can Be Redeclared

var color = 'red';
var color = 'blue'; // No error! Redeclaration allowed
console.log(color); // "blue"

4. Creates Property on Global Object (When Global)

var globalVar = "I'm global";
console.log(window.globalVar); // "I'm global" (in browser)

Problems with var:

// Problem 1: No block scope leads to confusion
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // All print 3, not 0, 1, 2!
  }, 100);
}

// Problem 2: Easy to accidentally redeclare
var user = 'John';
// ... 100 lines later
var user = 'Jane'; // Accidentally overwrote!

// Problem 3: Hoisting causes confusion
function confusing() {
  console.log(x); // undefined instead of error
  var x = 5;
}

let - The Modern Way

let was introduced in ES6 (2015) to fix the problems with var. It's the recommended way to declare variables that need to change.

Basic Usage:

let name = 'John';
let age = 30;
let isActive = true;

console.log(name); // "John"
age = 31; // Can reassign
console.log(age); // 31

Key Characteristics of let:

1. Block Scope

let is scoped to the nearest block (code between {}):

function example() {
  if (true) {
    let x = 10; // Scoped to the if block
    console.log(x); // 10
  }
  // console.log(x);  // ReferenceError: x is not defined
}

for (let i = 0; i < 3; i++) {
  console.log(i); // 0, 1, 2
}
// console.log(i);  // ReferenceError: i is not defined

2. No Hoisting (Actually, TDZ)

Technically let is hoisted, but it's not initialized until the declaration is reached:

// console.log(x);  // ReferenceError: Cannot access 'x' before initialization
let x = 10;
console.log(x); // 10

3. Cannot Be Redeclared in Same Scope

let color = 'red';
// let color = "blue";  // SyntaxError: Identifier 'color' has already been declared

// But you can declare in different scopes:
let y = 1;
if (true) {
  let y = 2; // Different scope - allowed
  console.log(y); // 2
}
console.log(y); // 1

4. Does NOT Create Global Object Property

let globalLet = "I'm not on window";
console.log(window.globalLet); // undefined (in browser)

let Solves var's Problems:

// Problem 1 SOLVED: Block scope works correctly
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i); // Correctly prints 0, 1, 2!
  }, 100);
}

// Problem 2 SOLVED: Can't accidentally redeclare
let user = 'John';
// let user = "Jane";  // SyntaxError!

// Problem 3 SOLVED: TDZ gives helpful error
function clear() {
  // console.log(x);  // ReferenceError (helpful!)
  let x = 5;
}

const - Constants

const declares variables whose reference cannot be reassigned. Introduced in ES6 alongside let.

Basic Usage:

const PI = 3.14159;
const COMPANY_NAME = 'TechCorp';
const MAX_SIZE = 100;

console.log(PI); // 3.14159
// PI = 3;                 // TypeError: Assignment to constant variable

Key Characteristics of const:

1. Must Be Initialized When Declared

const x = 10; // Valid
// const y;       // SyntaxError: Missing initializer in const declaration

2. Cannot Be Reassigned

const value = 10;
// value = 20;    // TypeError: Assignment to constant variable

const name = 'John';
// name = "Jane"; // TypeError

3. Block Scoped (Like let)

if (true) {
  const x = 10;
  console.log(x); // 10
}
// console.log(x);  // ReferenceError

4. Subject to TDZ (Like let)

// console.log(x);  // ReferenceError
const x = 10;

IMPORTANT: const Does NOT Mean Immutable!

const prevents reassignment, not mutation:

// Objects can be mutated
const person = { name: 'John', age: 30 };
person.age = 31; // Allowed! We're changing the property, not the reference
person.city = 'NYC'; // Allowed! Adding property
console.log(person); // { name: "John", age: 31, city: "NYC" }

// person = {};            // TypeError! Can't reassign the reference

// Arrays can be mutated
const numbers = [1, 2, 3];
numbers.push(4); // Allowed!
numbers[0] = 100; // Allowed!
console.log(numbers); // [100, 2, 3, 4]

// numbers = [5, 6, 7];    // TypeError! Can't reassign

When to Use const:

// Use const for:
const PI = 3.14159; // Mathematical constants
const API_URL = 'https://api.com'; // Configuration
const CONFIG = { debug: true }; // Objects that won't be reassigned
const DAYS = ['Mon', 'Tue', 'Wed']; // Arrays that won't be reassigned
const getUser = () => {}; // Functions

// The reference stays the same, even if contents change

Scope Differences

Understanding scope is crucial. Here's a comprehensive comparison:

Comparison Table:

Featurevarletconst
ScopeFunctionBlockBlock
HoistingYes (initialized as undefined)Yes (but in TDZ)Yes (but in TDZ)
RedeclarableYesNoNo
ReassignableYesYesNo
Global object propertyYesNoNo
TDZNoYesYes
Must initializeNoNoYes

Visual Scope Comparison:

// =============================================
// FUNCTION SCOPE (var) vs BLOCK SCOPE (let/const)
// =============================================

function scopeDemo() {
  // Function creates a scope for all three
  var functionVar = "I'm var";
  let blockLet = "I'm let";
  const blockConst = "I'm const";

  if (true) {
    // var: accessible, function scoped
    var innerVar = 'var in if';

    // let/const: only accessible in this block
    let innerLet = 'let in if';
    const innerConst = 'const in if';

    console.log(innerVar); // "var in if"
    console.log(innerLet); // "let in if"
    console.log(innerConst); // "const in if"
  }

  console.log(innerVar); // "var in if" āœ“ (function scoped)
  // console.log(innerLet);    // ReferenceError āœ— (block scoped)
  // console.log(innerConst);  // ReferenceError āœ— (block scoped)
}

Loop Scope:

// var in loops - shared variable
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var:', i), 100);
}
// Output: "var: 3", "var: 3", "var: 3" (all same!)

// let in loops - new variable each iteration
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let:', j), 100);
}
// Output: "let: 0", "let: 1", "let: 2" (correct!)

Nested Scopes:

let outer = 'outer';

function level1() {
  let level1Var = 'level 1';

  function level2() {
    let level2Var = 'level 2';

    // Can access all outer variables
    console.log(outer); // "outer"
    console.log(level1Var); // "level 1"
    console.log(level2Var); // "level 2"
  }

  level2();
  // console.log(level2Var);  // ReferenceError
}

level1();

Temporal Dead Zone (TDZ)

The Temporal Dead Zone is the period between entering a scope and the variable declaration being processed.

Understanding TDZ:

{
  // TDZ starts here for 'x'
  // console.log(x);  // ReferenceError: Cannot access 'x' before initialization
  // console.log(x);  // Still in TDZ
  // console.log(x);  // Still in TDZ

  let x = 10; // TDZ ends here

  console.log(x); // 10 - now accessible
}

TDZ Visualization:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  {                                                  │
│      ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”   │
│      │     TEMPORAL DEAD ZONE for 'x'          │   │
│      │     - x exists but can't be accessed    │   │
│      │     - accessing x throws ReferenceError │   │
│      ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜   │
│      let x = 10;  ← TDZ ends, x is initialized     │
│      console.log(x);  ← x is now accessible        │
│  }                                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

TDZ with var Comparison:

// var - hoisted and initialized as undefined
console.log(varVariable); // undefined (no error)
var varVariable = 'Hello';

// let - hoisted but in TDZ until declaration
// console.log(letVariable);   // ReferenceError!
let letVariable = 'Hello';

// const - hoisted but in TDZ until declaration
// console.log(constVariable); // ReferenceError!
const constVariable = 'Hello';

Why TDZ Exists:

TDZ helps catch programming errors:

// Without TDZ (var behavior) - bugs hide
function buggy() {
  console.log(x); // undefined - confusing!
  var x = 10;
}

// With TDZ (let behavior) - bugs are caught
function better() {
  // console.log(x);  // ReferenceError - helpful!
  let x = 10;
}

TDZ in Different Situations:

// TDZ with default parameters
// function broken(a = b, b = 1) {}  // ReferenceError: b is in TDZ
function working(a = 1, b = a) {
  return [a, b];
} // Works: a is initialized first

// TDZ with destructuring
// let { x = y, y = 1 } = {};  // ReferenceError: y is in TDZ
let { y = 1, x = y } = {}; // Works: y is initialized first

// TDZ in class
class MyClass {
  // x = this.y;  // Cannot access 'y' before initialization
  y = 10;
  x = this.y; // Works if y is declared first
}

Best Practices

1. Default to const

// Start with const - most variables don't need reassignment
const userName = 'John';
const config = { debug: true };
const numbers = [1, 2, 3];

// Change to let only when you need reassignment
let counter = 0;
counter++; // Need to reassign, so use let

2. Never Use var

// āŒ Avoid var
var oldWay = 'problematic';

// āœ… Use let or const
let newWay = 'better';
const bestWay = 'recommended';

3. Declare at the Top of Scope

// āœ… Good - declarations at top
function process(items) {
  const result = [];
  let current;

  for (let i = 0; i < items.length; i++) {
    current = items[i];
    result.push(current * 2);
  }

  return result;
}

4. One Variable Per Declaration

// āŒ Multiple declarations on one line
let a = 1,
  b = 2,
  c = 3;

// āœ… One per line - easier to read and modify
let a = 1;
let b = 2;
let c = 3;

5. Use Meaningful Names

// āŒ Cryptic names
const x = 86400;
let t = true;

// āœ… Descriptive names
const SECONDS_PER_DAY = 86400;
let isUserLoggedIn = true;

Quick Decision Guide:

Start with: const
    ↓
Need to reassign?
    ↓
   YES → use let
    ↓
   NO → keep const

Never use var!

šŸŽÆ Key Takeaways

  1. •var - Function scoped, hoisted, can be redeclared (avoid using)
  2. •let - Block scoped, TDZ applies, cannot be redeclared (use for reassignable variables)
  3. •const - Block scoped, TDZ applies, cannot be reassigned (use as default)
  4. •TDZ prevents accessing let/const before declaration (catches bugs)
  5. •Always use const by default, switch to let when reassignment is needed

āž”ļø Next Topic

Continue to 2.2 Data Types to learn about the different types of values JavaScript can work with!

Module 02 Variables And Data Types - JavaScript Tutorial | DeepML