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 theif
block refers to the samex
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 theif
block is a new variable scoped to that block, separate from thex
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 withconst
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 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
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
andconst
. - 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 withundefined
. - 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 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
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 withundefined
.- Attempting to call
sayHi
before assignment results in aTypeError
.
Temporal Dead Zone (TDZ)
Understanding TDZ
- The period between the hoisting of a variable (with
let
orconst
) 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
andconst
.
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 thex
variable in the function scope.let y = 200;
andconst z = 300;
are new variables scoped to the block.- Therefore, inside the block,
x
is100
,y
is200
, andz
is300
.
-
Outside the block:
x
remains100
becausevar
is function-scoped, and the assignment inside the block overwrote the originalx
.y
andz
outside the block retain their original values (2
and3
) because thelet
andconst
declarations inside the block are scoped to the block and do not affect the outery
andz
.
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 withundefined
.- Outputs
undefined
.
-
Second
console.log(b);
let b
is hoisted but not initialized (TDZ applies).- Accessing
b
before initialization results in aReferenceError
.
-
Inside
foo()
function:console.log(c);
var c
is 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.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 aSyntaxError: Identifier 'y' has already been declared
. -
Line 3 (
z = 2;
): Error.const
variables cannot be reassigned. This results in aTypeError: Assignment to constant variable.
-
Line 4 (
const w;
): Error.const
declarations 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 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 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 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 thefoo
function declaration with the new function expression.
- The variable assignment
-
Line 2 (
foo();
):- Now,
foo
refers 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...
Explain hoisting in JavaScript and how it affects variable declarations with `var`, `let`, and `const`.
Loading...
What is the Temporal Dead Zone (TDZ) in JavaScript?
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.