Variables (let, const, var)

1. Welcome and Icebreaker:

  • Welcome! What’s the trickiest bug you’ve encountered that was related to variable scope or reassignment?

2. Outline the Session:

  • Today, we’ll explore variable declarations in JavaScript and TypeScript: let, const, and var.
  • We will focus on their scope, hoisting (for var), and reassignment/re-declaration differences.
  • Learning Outcomes: Understand how to choose the appropriate variable declaration keyword for different scenarios.
  • Feel free to jump in with questions.

3. Concept Explanation:

  • In JavaScript (and thus TypeScript), variables are used to store data values. How you declare a variable affects its behavior, particularly its scope and whether it can be reassigned or re-declared.

  • var (The Old Way - Avoid in Modern JavaScript/TypeScript):

    • Scope: Function-scoped or globally-scoped. If declared outside any function, it’s global. If inside a function, it’s accessible anywhere within that function.
    • Hoisting: var declarations are hoisted to the top of their scope (function or global) and initialized with undefined before the code is executed. This means you can use a variable before its textual declaration in the code, though its value will be undefined until the assignment.
    • Re-declaration: Can be re-declared within the same scope without error.
    • Reassignment: Can be reassigned.
  • let (The Modern Way - Preferred for Variables That Change):

    • Scope: Block-scoped ({}). A variable declared with let is only accessible within the block of code (e.g., inside an if statement, for loop, or a standalone block) where it is defined.
    • Hoisting: let declarations are hoisted, but not initialized. Accessing them before the declaration results in a ReferenceError (this is often called the “Temporal Dead Zone”).
    • Re-declaration: Cannot be re-declared within the same scope.
    • Reassignment: Can be reassigned.
  • const (The Modern Way - Preferred for Variables That Don’t Change):

    • Scope: Block-scoped ({}), just like let.
    • Hoisting: Similar to let, const declarations are hoisted but not initialized, leading to a Temporal Dead Zone.
    • Re-declaration: Cannot be re-declared within the same scope.
    • Reassignment: Cannot be reassigned after initialization. It must be initialized at the time of declaration.
      • Important Note for Objects/Arrays: For const variables holding objects or arrays, the variable itself cannot be reassigned to a new object or array. However, the properties of the object or the elements of the array can be modified.
  • TypeScript and Variables: TypeScript uses these same JavaScript rules but adds type annotations. The choice between let, const, and var is primarily about scope and mutability, not types.

  • Recommendation: Prefer const by default. Use let only when you know a variable’s value needs to change. Avoid var in modern codebases to prevent common bugs related to its scoping and hoisting behavior.

4. Live Coding Demonstration:

console.log("--- var examples ---");
function varScopeTest() {
  if (true) {
    var x = 10;
    console.log("Inside if (var x):", x); // 10
  }
  console.log("Outside if, inside function (var x):", x); // 10 (function-scoped)

  // Hoisting with var
  console.log("y before declaration (var y):", y); // undefined
  var y = 20;
  console.log("y after declaration (var y):", y); // 20

  var z = 30;
  var z = 40; // Re-declaration allowed
  console.log("z after re-declaration (var z):", z); // 40
  z = 50; // Reassignment allowed
  console.log("z after reassignment (var z):", z); // 50
}
varScopeTest();
// console.log(x); // Error: x is not defined (outside function scope)

console.log("\n--- let examples ---");
function letScopeTest() {
  if (true) {
    let a: number = 100;
    console.log("Inside if (let a):", a); // 100
    // let a: number = 150; // Error: Cannot redeclare block-scoped variable 'a'.
  }
  // console.log("Outside if, inside function (let a):", a); // Error: Cannot find name 'a'. (block-scoped)

  // Temporal Dead Zone with let
  // console.log(b); // ReferenceError: Cannot access 'b' before initialization
  let b: number = 200;
  console.log("b after declaration (let b):", b); // 200

  let c: string = "hello";
  // let c: string = "world"; // Error: Cannot redeclare block-scoped variable 'c'.
  c = "world"; // Reassignment allowed
  console.log("c after reassignment (let c):", c); // world
}
letScopeTest();

console.log("\n--- const examples ---");
function constScopeTest() {
  const PI: number = 3.14159;
  console.log("PI (const):", PI);
  // PI = 3.14; // Error: Cannot assign to 'PI' because it is a constant.
  // const PI: number = 3.14; // Error: Cannot redeclare block-scoped variable 'PI'.

  if (true) {
    const G: number = 9.8;
    console.log("Inside if (const G):", G);
  }
  // console.log("Outside if (const G):", G); // Error: Cannot find name 'G'. (block-scoped)

  // Const with objects and arrays
  const person: { name: string; age: number } = {
    name: "Alice",
    age: 30
  };
  console.log("Person (const object):", person);

  // We can modify properties of a const object
  person.name = "Bob";
  person.age = 31;
  console.log("Modified Person (const object):", person);

  // But we cannot reassign the const variable itself
  // person = { name: "Charlie", age: 32 }; // Error: Cannot assign to 'person' because it is a constant.

  const numbers: number[] = [1, 2, 3];
  console.log("Numbers (const array):", numbers);

  // We can modify the array (e.g., push new elements, change elements)
  numbers.push(4);
  numbers[0] = 0;
  console.log("Modified Numbers (const array):", numbers);

  // But we cannot reassign the const variable to a new array
  // numbers = [5, 6, 7]; // Error: Cannot assign to 'numbers' because it is a constant.
}
constScopeTest();

// Global scope example
let globalLet = "I am global let";
const globalConst = "I am global const";
var globalVar = "I am global var";

{
  let blockLet = "I am block let";
  const blockConst = "I am block const";
  var blockVar = "I am block var (but actually function/global scoped)";
  console.log(blockLet);
  console.log(blockConst);
}
// console.log(blockLet); // Error
// console.log(blockConst); // Error
console.log(blockVar); // "I am block var..." - accessible if this code is not in a function
                       // If this whole script is treated as a module, `var` might behave differently
                       // than traditional script-tag global `var`.

5. Tasks:

  • Short Exercise:
    1. Declare a variable count using let and initialize it to 0. Increment it and print its value.
    2. Declare a variable appName using const and initialize it to your favorite application’s name. Try to reassign it and observe the error.
    3. Create an object userDetails using const with properties id (number) and username (string). Modify the username and print the object. Try to reassign userDetails to a new object and observe the error.
    4. Write a for loop that uses let to declare its counter. Inside the loop, print the counter. After the loop, try to print the counter again and observe if it’s accessible.
    5. (Challenge) Write a function that demonstrates the difference in scope between var and let within an if block inside that function.