JavaScript Scope and Hoisting

Two fundamental concepts that affect how variables behave in JavaScript are scope and hoisting. Understanding them is essential for writing bug-free, predictable code.

What is Scope?

Scope defines where a variable is accessible in code. A variable declared in one part of the code may or may not be accessible in another part — that depends on its scope.

Real-life analogy: Variables are like people with access cards. A global employee can enter any room. A local employee can only enter their own department. Scope defines which "rooms" (code blocks) a variable can enter.

Types of Scope

  • Global Scope — Available everywhere in the program
  • Function Scope — Available only inside the function
  • Block Scope — Available only inside the block { }

Global Scope

A variable declared outside of any function or block is in the global scope. It can be accessed from anywhere in the code.

let appName = "eStudy247";  // Global variable

function displayName() {
  console.log(appName);  // Accessible inside function
}

displayName();  // eStudy247
console.log(appName);  // Also accessible here

Function Scope

Variables declared inside a function are only accessible within that function. They cease to exist once the function finishes executing.

function calculateTax() {
  let taxRate = 0.18;  // Local to this function
  console.log(taxRate);  // Works fine here
}

calculateTax();  // 0.18
// console.log(taxRate);  // Error! taxRate is not defined here

Each function has its own scope. Variables with the same name in different functions do not conflict.

function functionA() {
  let message = "From A";
  console.log(message);  // From A
}

function functionB() {
  let message = "From B";  // Different variable, same name — no conflict
  console.log(message);  // From B
}

functionA();
functionB();

Block Scope (let and const)

With let and const, a variable declared inside any code block { } is only accessible within that block. This is called block scope.

if (true) {
  let blockVar = "I am inside the block";
  console.log(blockVar);  // Works here
}

// console.log(blockVar);  // Error! blockVar is not accessible outside the block
for (let i = 0; i < 3; i++) {
  console.log(i);  // Works: 0, 1, 2
}
// console.log(i);  // Error! i only exists inside the for loop

var Has No Block Scope

This is why var can cause unexpected bugs — it ignores block boundaries.

if (true) {
  var leaked = "I escaped the block!";
}
console.log(leaked);  // Works! (var leaks out of the block)

if (true) {
  let safe = "I stay inside.";
}
// console.log(safe);  // Error! (let respects the block)

Scope Chain

When JavaScript looks for a variable, it starts in the current scope and moves outward (to parent scopes) until it either finds the variable or reaches the global scope. This process is called the scope chain.

let level = "global";

function outer() {
  let level = "outer function";

  function inner() {
    // No local "level" here — JavaScript looks in parent scopes
    console.log(level);  // "outer function" (found in outer scope)
  }

  inner();
}

outer();

Lexical Scope

JavaScript uses lexical (static) scoping — a function's scope is determined by where it is defined, not where it is called.

let country = "India";

function showCountry() {
  console.log(country);  // Uses the "country" from where the function was defined
}

function changeContext() {
  let country = "Nepal";
  showCountry();  // Still prints "India" — not "Nepal"
}

changeContext();  // India

What is Hoisting?

Hoisting is JavaScript's default behavior of moving declarations to the top of their scope before the code executes. This means some variables and functions can be used before they appear in the source code.

Function Hoisting

Function declarations are fully hoisted — they can be called before they are defined.

greet();  // Works! Even though function is defined below

function greet() {
  console.log("Hello from hoisted function!");
}
// Output: Hello from hoisted function!

var Hoisting

Variables declared with var are hoisted to the top, but only the declaration — not the assigned value. The variable exists but holds undefined until the assignment line is reached.

console.log(message);  // undefined (not an error, but not the value either)
var message = "Hello";
console.log(message);  // Hello

This is what JavaScript actually does internally with var:

var message;           // Declaration hoisted to the top
console.log(message);  // undefined
message = "Hello";     // Assignment stays in place
console.log(message);  // Hello

let and const Hoisting — Temporal Dead Zone

let and const are also hoisted, but they are NOT initialized. Accessing them before their declaration line causes a ReferenceError. The period between the start of the scope and the variable's declaration is called the Temporal Dead Zone (TDZ).

// console.log(name);  // ReferenceError: Cannot access 'name' before initialization

let name = "Pooja";
console.log(name);  // Pooja

Function Expression and Arrow Function Hoisting

Function expressions and arrow functions are NOT hoisted. They behave like regular variables.

// greet();  // Error! Cannot access before initialization

const greet = () => {
  console.log("Hello!");
};

greet();  // Works after declaration

Hoisting Summary Table

Declaration TypeHoisted?Initial Value Before Declaration
varYesundefined
letYes (TDZ)ReferenceError
constYes (TDZ)ReferenceError
function declarationYes (fully)Full function available
function expression / arrowNoReferenceError

Practical Example — Scope in Action

let total = 0;  // Global

function addToCart(price) {
  let discount = 10;           // Local to addToCart
  total += price - discount;   // Accesses global "total"
}

addToCart(100);  // total = 90
addToCart(200);  // total = 90 + 190 = 280

console.log(total);  // 280
// console.log(discount);  // Error — not accessible outside function

Key Points to Remember

  • Scope controls where variables are accessible in code
  • Global variables are accessible everywhere; local variables are limited to their function or block
  • let and const are block-scoped; var is function-scoped
  • Avoid var — block-scope leaking causes hard-to-find bugs
  • Hoisting moves declarations to the top of their scope before execution
  • var is hoisted with value undefined; accessing before assignment returns undefined
  • let and const are in the Temporal Dead Zone until their declaration — accessing them before causes a ReferenceError
  • Function declarations are fully hoisted — they can be called before they are written

Leave a Comment

Your email address will not be published. Required fields are marked *