Variable Scope and Hoisting in JavaScript

Understanding variable scope and hoisting is fundamental to mastering JavaScript. These concepts influence how variables are accessed and manipulated throughout your code. This lesson provides an in-depth exploration of the differences between var, let, and const, their scoping rules, and how hoisting affects them.

Variable Declarations: var, let, and const

var Declaration

  • Scope: Function-scoped or globally scoped if not inside a function.
  • Hoisting: Declarations are hoisted to the top of their scope and initialized with undefined.
  • Redeclaration: Can be redeclared within the same scope without errors.
  • Usage: Was the primary way to declare variables before ES6.

Example:

function example() {
  var x = 10
  if (true) {
    var x = 20 // Same variable as above
    console.log(x) // Outputs: 20
  }
  console.log(x) // Outputs: 20
}
example()

Explanation:

  • var x inside the if block refers to the same x declared at the function level due to function scope.

let Declaration

  • Scope: Block-scoped (enclosed in {}), including functions, loops, and conditionals.
  • Hoisting: Declarations are hoisted but not initialized, leading to the Temporal Dead Zone (TDZ).
  • Redeclaration: Cannot be redeclared in the same scope; attempting to do so results in a SyntaxError.
  • Usage: Introduced in ES6 to address scoping issues with var.

Example:

function example() {
  let x = 10
  if (true) {
    let x = 20 // Different variable from the one above
    console.log(x) // Outputs: 20
  }
  console.log(x) // Outputs: 10
}
example()

Explanation:

  • The let x inside the if block is a new variable scoped to that block, separate from the x declared in the function scope.

const Declaration

  • Scope: Block-scoped, similar to let.
  • Hoisting: Also subject to the Temporal Dead Zone.
  • Assignment: Must be initialized at the time of declaration.
  • Immutability: Variables declared with const cannot be reassigned. However, objects and arrays declared with const can have their contents mutated.
  • Usage: Used for variables that should not be reassigned, promoting immutability.

Example:

const x = 10
x = 20 // TypeError: Assignment to constant variable.

Explanation:

  • Reassigning a const variable results in a TypeError.

Mutable Objects with const:

const person = { name: 'Alice' }
person.name = 'Bob' // Allowed
person = { name: 'Charlie' } // TypeError

Explanation:

  • You can modify the properties of a const object, but you cannot reassign the variable to a new object.

Variable Scope

Global Scope

  • Variables declared outside any function or block.
  • Accessible from anywhere in the code.
  • In browsers, global variables become properties of the window object.

Example:

var globalVar = 'I am global'

function showGlobal() {
  console.log(globalVar)
}

showGlobal() // Outputs: I am global

Function Scope

  • Variables declared within a function using var are scoped to that function.
  • Not accessible outside the function.

Example:

function myFunction() {
  var functionVar = 'I am inside a function'
  console.log(functionVar)
}

myFunction() // Outputs: I am inside a function
console.log(functionVar) // ReferenceError: functionVar is not defined

Block Scope

  • Introduced with let and const.
  • Variables declared within a block {} are only accessible within that block.

Example:

if (true) {
  let blockVar = 'I am inside a block'
  console.log(blockVar) // Outputs: I am inside a block
}

console.log(blockVar) // ReferenceError: blockVar is not defined

Hoisting

What is Hoisting?

Hoisting is JavaScript's default behavior of moving declarations to the top of their scope before code execution. This means variables and function declarations are processed before any code is executed.

Hoisting with var

  • Variables declared with var are hoisted to the top of their function scope.
  • Initialized with undefined during the creation phase.

Example:

console.log(a) // Outputs: undefined
var a = 5
console.log(a) // Outputs: 5

Explanation:

  • The declaration var a is hoisted and initialized with undefined.
  • The assignment a = 5 happens during the execution phase.

Hoisting with let and const

  • Declarations are hoisted but not initialized.
  • Accessing them before initialization results in a ReferenceError due to the Temporal Dead Zone (TDZ).

Example:

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

Explanation:

  • let b is hoisted but not initialized.
  • Attempting to access b before its declaration results in a ReferenceError.

Function Hoisting

  • Function declarations are hoisted entirely, including their body.
  • Allows functions to be called before they are defined in the code.

Example:

greet() // Outputs: Hello!

function greet() {
  console.log('Hello!')
}

Explanation:

  • The entire function greet is hoisted, so it can be called before its declaration.

Function Expressions and Hoisting

  • Variables declared with var and assigned function expressions are hoisted, but only the variable declaration is hoisted, not the assignment.
  • Results in undefined when accessed before assignment.

Example:

sayHi() // TypeError: sayHi is not a function

var sayHi = function () {
  console.log('Hi!')
}

Explanation:

  • var sayHi is hoisted and initialized with undefined.
  • Attempting to call sayHi before assignment results in a TypeError.

Temporal Dead Zone (TDZ)

Understanding TDZ

  • The period between the hoisting of a variable (with let or const) and its initialization.
  • During TDZ, accessing the variable results in a ReferenceError.

Example:

{
  console.log(x) // ReferenceError
  let x = 5
}

Explanation:

  • x is hoisted but not initialized.
  • Accessing x before initialization is within the TDZ.

Redeclaration and Reassignment

var

  • Redeclaration: Allowed within the same scope.
  • Reassignment: Allowed.

Example:

var a = 1
var a = 2 // Redeclaration is allowed
a = 3 // Reassignment is allowed
console.log(a) // Outputs: 3

let

  • Redeclaration: Not allowed within the same scope; throws a SyntaxError.
  • Reassignment: Allowed.

Example:

let b = 1
let b = 2 // SyntaxError: Identifier 'b' has already been declared
b = 3 // Reassignment is allowed
console.log(b) // Outputs: 3

const

  • Redeclaration: Not allowed.
  • Reassignment: Not allowed; variables are read-only.

Example:

const c = 1
c = 2 // TypeError: Assignment to constant variable.
const c = 3 // SyntaxError: Identifier 'c' has already been declared

Best Practices

Prefer const and let over var

  • Use const by default for variables that won't be reassigned.
  • Use let for variables that will be reassigned.
  • Avoid var to prevent scope-related bugs.

Minimize Global Variables

  • Limit the use of global variables to avoid conflicts and unintended side effects.
  • Encapsulate code within functions or modules.

Understand Scope

  • Be mindful of where variables are declared and their accessibility.
  • Use block scope to prevent variable leakage outside intended blocks.

Avoid Hoisting Confusion

  • Declare variables at the top of their scope.
  • Initialize variables when they are declared to avoid the TDZ with let and const.

Immutable Data

  • Use const to declare constants and promote immutability.
  • For objects and arrays, use methods that do not mutate the original data (e.g., spread operator, Object.assign).

Exercises

Exercise 1: Scoping with var, let, and const

Question:

What will be the output of the following code? Explain why.

function testScope() {
  var x = 1
  let y = 2
  const z = 3

  {
    var x = 100
    let y = 200
    const z = 300
    console.log('Inside block:', x, y, z)
  }

  console.log('Outside block:', x, y, z)
}

testScope()

Answer:

Output:

Inside block: 100 200 300
Outside block: 100 2 3

Explanation:

  • Inside the block:

    • var x = 100; redeclares and overwrites the x variable in the function scope.
    • let y = 200; and const z = 300; are new variables scoped to the block.
    • Therefore, inside the block, x is 100, y is 200, and z is 300.
  • Outside the block:

    • x remains 100 because var is function-scoped, and the assignment inside the block overwrote the original x.
    • y and z outside the block retain their original values (2 and 3) because the let and const declarations inside the block are scoped to the block and do not affect the outer y and z.

Exercise 2: Hoisting Behavior

Question:

Predict the output of the following code and explain the behavior.

console.log(a) // ?
var a = 10

console.log(b) // ?
let b = 20

function foo() {
  console.log(c) // ?
  var c = 30
}
foo()

Answer:

Output:

undefined
ReferenceError: Cannot access 'b' before initialization
undefined

Explanation:

  • First console.log(a);

    • var a is hoisted and initialized with undefined.
    • Outputs undefined.
  • Second console.log(b);

    • let b is hoisted but not initialized (TDZ applies).
    • Accessing b before initialization results in a ReferenceError.
  • Inside foo() function:

    • console.log(c);
      • var c is hoisted within the function and initialized with undefined.
      • Outputs undefined.

Exercise 3: Redeclaration and Reassignment

Question:

Which of the following lines will throw an error? Explain why.

var x = 1;
var x = 2; // Line 1

let y = 1;
let y = 2; // Line 2

const z = 1;
z = 2;     // Line 3

const w;   // Line 4

Answer:

  • Line 1 (var x = 2;): No error. var allows redeclaration and reassignment in the same scope.

  • Line 2 (let y = 2;): Error. let does not allow redeclaration in the same scope. This results in a SyntaxError: Identifier 'y' has already been declared.

  • Line 3 (z = 2;): Error. const variables cannot be reassigned. This results in a TypeError: Assignment to constant variable.

  • Line 4 (const w;): Error. const declarations must be initialized at the time of declaration. This results in a SyntaxError: Missing initializer in const declaration.

Exercise 4: Temporal Dead Zone

Question:

What will be the output of the following code?

{
  console.log(a) // Line A
  let a = 5
  console.log(a) // Line B
}

Answer:

  • Line A: Throws ReferenceError: Cannot access 'a' before initialization due to the Temporal Dead Zone.

  • Line B: Would output 5 if Line A didn't cause an error.

Explanation:

  • let a is hoisted but not initialized.
  • Accessing a before initialization (Line A) is within the TDZ and results in a ReferenceError.

Exercise 5: Function Hoisting

Question:

Consider the following code. What will be the output?

foo() // Line 1

var foo = function () {
  console.log('foo')
}

function foo() {
  console.log('FOO')
}

foo() // Line 2

Answer:

Output:

FOO
foo

Explanation:

  • Line 1 (foo();):

    • Function declarations are hoisted before variable declarations.
    • The function foo() declaration is hoisted and assigned first.
    • The variable declaration var foo is hoisted but the assignment happens during execution.
    • At Line 1, foo refers to the hoisted function declaration.
    • Outputs FOO.
  • After the declarations:

    • The variable assignment foo = function() { ... } overwrites the foo function declaration with the new function expression.
  • Line 2 (foo();):

    • Now, foo refers to the function expression assigned to the variable foo.
    • Outputs foo.

Understanding variable scope, hoisting, and the differences between var, let, and const is fundamental to writing robust JavaScript code. By mastering these concepts, you'll be better equipped to avoid common pitfalls, write more maintainable code, and handle scope-related questions in technical interviews with confidence.

Practice Problems

What is the difference between `var`, `let`, and `const` in JavaScript?

Loading...

Explain hoisting in JavaScript and how it affects variable declarations with `var`, `let`, and `const`.

Loading...

Temporal Dead Zone in JSDifficulty: Medium

What is the Temporal Dead Zone (TDZ) in JavaScript?

Loading...

Reassigning let and ConstDifficulty: Medium

Can you reassign and redeclare variables declared with `let` and `const`?

Loading...

Let's continue exploring the next page. Take your time, and proceed when you're ready.

Lesson completed?

Found a bug, typo, or have feedback?

Let me know