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 xinside theifblock refers to the samexdeclared 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 xinside theifblock is a new variable scoped to that block, separate from thexdeclared 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
constcannot be reassigned. However, objects and arrays declared withconstcan 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
constvariable results in aTypeError.
Mutable Objects with const:
const person = { name: 'Alice' }
person.name = 'Bob' // Allowed
person = { name: 'Charlie' } // TypeError
Explanation:
- You can modify the properties of a
constobject, 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
windowobject.
Example:
var globalVar = 'I am global'
function showGlobal() {
console.log(globalVar)
}
showGlobal() // Outputs: I am global
Function Scope
- Variables declared within a function using
varare 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
letandconst. - 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
varare hoisted to the top of their function scope. - Initialized with
undefinedduring the creation phase.
Example:
console.log(a) // Outputs: undefined
var a = 5
console.log(a) // Outputs: 5
Explanation:
- The declaration
var ais hoisted and initialized withundefined. - The assignment
a = 5happens during the execution phase.
Hoisting with let and const
- Declarations are hoisted but not initialized.
- Accessing them before initialization results in a
ReferenceErrordue to the Temporal Dead Zone (TDZ).
Example:
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 10
Explanation:
let bis hoisted but not initialized.- Attempting to access
bbefore its declaration results in aReferenceError.
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
greetis hoisted, so it can be called before its declaration.
Function Expressions and Hoisting
- Variables declared with
varand assigned function expressions are hoisted, but only the variable declaration is hoisted, not the assignment. - Results in
undefinedwhen accessed before assignment.
Example:
sayHi() // TypeError: sayHi is not a function
var sayHi = function () {
console.log('Hi!')
}
Explanation:
var sayHiis hoisted and initialized withundefined.- Attempting to call
sayHibefore assignment results in aTypeError.
Temporal Dead Zone (TDZ)
Understanding TDZ
- The period between the hoisting of a variable (with
letorconst) and its initialization. - During TDZ, accessing the variable results in a
ReferenceError.
Example:
{
console.log(x) // ReferenceError
let x = 5
}
Explanation:
xis hoisted but not initialized.- Accessing
xbefore 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
constby default for variables that won't be reassigned. - Use
letfor variables that will be reassigned. - Avoid
varto 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
letandconst.
Immutable Data
- Use
constto 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 thexvariable in the function scope.let y = 200;andconst z = 300;are new variables scoped to the block.- Therefore, inside the block,
xis100,yis200, andzis300.
-
Outside the block:
xremains100becausevaris function-scoped, and the assignment inside the block overwrote the originalx.yandzoutside the block retain their original values (2and3) because theletandconstdeclarations inside the block are scoped to the block and do not affect the outeryandz.
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 ais hoisted and initialized withundefined.- Outputs
undefined.
-
Second
console.log(b);let bis hoisted but not initialized (TDZ applies).- Accessing
bbefore initialization results in aReferenceError.
-
Inside
foo()function:console.log(c);var cis hoisted within the function and initialized withundefined.- 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.varallows redeclaration and reassignment in the same scope. -
Line 2 (
let y = 2;): Error.letdoes not allow redeclaration in the same scope. This results in aSyntaxError: Identifier 'y' has already been declared. -
Line 3 (
z = 2;): Error.constvariables cannot be reassigned. This results in aTypeError: Assignment to constant variable. -
Line 4 (
const w;): Error.constdeclarations must be initialized at the time of declaration. This results in aSyntaxError: 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 initializationdue to the Temporal Dead Zone. -
Line B: Would output
5if Line A didn't cause an error.
Explanation:
let ais hoisted but not initialized.- Accessing
abefore initialization (Line A) is within the TDZ and results in aReferenceError.
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 foois hoisted but the assignment happens during execution. - At Line 1,
foorefers to the hoisted function declaration. - Outputs
FOO.
-
After the declarations:
- The variable assignment
foo = function() { ... }overwrites thefoofunction declaration with the new function expression.
- The variable assignment
-
Line 2 (
foo();):- Now,
foorefers to the function expression assigned to the variablefoo. - Outputs
foo.
- Now,
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...
What is the Temporal Dead Zone (TDZ) in JavaScript?
Loading...
Explain hoisting in JavaScript and how it affects variable declarations with `var`, `let`, and `const`.
Loading...
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.