JavaScript Scope and Hoisting Explained (With Examples & Interview Questions)

Scope & Hoisting in JavaScript Explained — Complete Guide
πŸ”­ Scope & Hoisting · Complete Guide

Scope & Hoisting in
JavaScript Explained

Master JavaScript scope and hoisting completely — global, local, block scope, var vs let vs const differences, hoisting rules, temporal dead zone, execution context, common mistakes & interview questions.

⏱️ 10 min read πŸ”­ Full Details πŸ’» Code + Output πŸ’Ό Interview Questions

What is Scope in JavaScript? — The Complete Explanation

Scope in JavaScript determines where a variable can be accessed in your code. It defines which parts of your program can read or modify a variable — and which parts cannot. Scope is one of the most fundamental concepts in JavaScript, and misunderstanding it causes some of the most confusing bugs that developers encounter at every level.

Think of scope like the rooms in a house. A variable declared inside a specific room (inside a function or block) can only be used inside that room. A variable declared in the hallway (global scope) is accessible from every room. The rules that determine what is accessible from where — that is scope.

JavaScript has three types of scope: Global Scope (accessible everywhere), Local/Function Scope (accessible only inside a function), and Block Scope (accessible only inside a { } block). Understanding how var, let, and const behave differently with scope is absolutely essential for writing correct, professional JavaScript code in 2026.

🌍

Global Scope — Variables Accessible Everywhere

A variable has global scope when it is declared outside all functions and blocks — at the top level of your JavaScript file. Global variables can be read and modified from anywhere in your entire program — inside functions, inside loops, inside conditions — absolutely everywhere.

While global variables are convenient, using too many of them is considered bad practice in professional JavaScript. Any function in your program can accidentally overwrite a global variable, causing hard-to-find bugs. This is why experienced developers minimize global variables and prefer function or block scope instead.

// Global scope — declared outside everything

let   appName   = "MyApp";

const MAX_USERS = 100;

var   userCount = 0;

function addUser(name) {

  userCount++;  // ✅ Can access global userCount

  console.log(`${appName}: "${name}" added. Total: ${userCount}`);

}

function checkCapacity() {

  if (userCount >= MAX_USERS) {

    console.log("❌ App is full!");

  } else {

    console.log(`✅ ${MAX_USERS - userCount} spots remaining`);

  }

}

addUser("Sara");

addUser("Ali");

checkCapacity();

console.log(appName, userCount);  // MyApp 2
▶ Output
MyApp: "Sara" added. Total: 1 MyApp: "Ali" added. Total: 2 ✅ 98 spots remaining MyApp 2
fn()

Local Scope (Function Scope) — Private Variables Inside Functions

A variable has local scope (also called function scope) when it is declared inside a function. It exists only while that function is running and is completely invisible outside. Once the function finishes executing, the local variable is destroyed and the memory is freed.

Local variables provide privacy and isolation. Two functions can have variables with the exact same name and they will never interfere with each other — because each variable lives in its own separate local scope. This is a critical feature for writing safe, maintainable code.

function calculateTax(salary) {

  let taxRate = 0.2;          // local — only inside calculateTax

  let tax     = salary * taxRate;

  console.log("Tax Rate: 20% | Tax: " + tax);

  return salary - tax;

}

function calculateDiscount(price) {

  let taxRate  = 0.1;          // same name — but DIFFERENT local variable!

  let discount = price * taxRate;

  console.log("Discount Rate: 10% | Discount: " + discount);

  return price - discount;

}

console.log(calculateTax(50000));      // Tax: 10000 → returns 40000

console.log(calculateDiscount(2000));  // Discount: 200 → returns 1800

// These two taxRate variables NEVER conflict! πŸŽ‰

// console.log(taxRate);  ← ReferenceError — does not exist outside ✅
▶ Output
Tax Rate: 20% | Tax: 10000 40000 Discount Rate: 10% | Discount: 200 1800
{ }

Block Scope — let and const Stay Inside { } Blocks

Block scope was introduced with ES6 (2015) through let and const. A variable has block scope when it is declared inside a pair of curly braces { } — such as inside an if statement, a for loop, a while loop, or any standalone block. Variables declared with let or const stay inside their block. Variables declared with var completely ignore block boundaries and leak out.

// BLOCK SCOPE — let/const stay inside { }, var leaks out

if (true) {

  let   blockLet   = "I am block scoped with let";

  const blockConst = "I am block scoped with const";

  var   blockVar   = "I LEAK outside with var!";

  console.log(blockLet);    // ✅ works inside the block

  console.log(blockConst);  // ✅ works inside the block

}

console.log(blockVar);      // ✅ var leaks: "I LEAK outside with var!"

// console.log(blockLet);   ← ReferenceError — let respects block scope ✅

// console.log(blockConst); ← ReferenceError — const respects block scope ✅

// Block scope in for loops — critical difference!

for (let i = 0; i < 3; i++) {

  console.log("let i: " + i);  // 0, 1, 2 — i exists only in loop

}

// console.log(i);  ← ReferenceError — let i is block scoped ✅

for (var j = 0; j < 3; j++) {

  console.log("var j: " + j);

}

console.log("After loop, var j is: " + j);  // 3 — var leaked out!
▶ Output
I am block scoped with let I am block scoped with const I LEAK outside with var! let i: 0 let i: 1 let i: 2 var j: 0 var j: 1 var j: 2 After loop, var j is: 3
⚠️ var leaks from blocks — always use let in loops! The var inside a for loop leaks out and persists after the loop ends. This is a major source of bugs in older JavaScript code. Always use let for loop counters and inside all conditional blocks.
πŸ“Š

var vs let vs const — Complete Scope Comparison

Featurevarletconst
Scope typeFunction scopedBlock scopedBlock scoped
Leaks from if/for blocks?✅ Yes — leaks!❌ No — contained❌ No — contained
Re-declarable?Yes (dangerous)NoNo
Hoisted asundefinedTDZ errorTDZ error
Reassignable?YesYesNo
Use in 2026?❌ Avoid always✅ When value changes✅ Default choice
πŸ—️

What is Hoisting? — JavaScript Moves Declarations Up

Hoisting is JavaScript's automatic behavior of moving variable and function declarations to the top of their scope before the code runs. This happens during the compilation phase — before a single line of your code actually executes.

The important detail: only the declaration is hoisted — not the value assignment. This is why accessing a var variable before its line returns undefined (not an error). And this is why you can call a function declared with the function keyword before it appears in your code — the entire function was hoisted.

// ✅ Function DECLARATIONS are FULLY hoisted

sayHello();   // Works! Called BEFORE it is defined below

function sayHello() {

  console.log("Hello! I was hoisted! πŸ‘‹");

}

// var — declaration hoisted, but value is NOT

console.log(score);   // undefined — hoisted but not yet assigned

var score = 100;

console.log(score);   // 100 — now it has the value

// What JavaScript actually sees after hoisting:

// var score;          ← moved to top (declaration only)

// console.log(score); ← undefined

// score = 100;        ← assignment stays in original place

// ❌ Arrow function / Function EXPRESSION — NOT hoisted

// greet();  ← TypeError: greet is not a function

const greet = () => console.log("Hi from arrow! πŸš€");

greet();   // ✅ Works only AFTER the definition

// Class declarations — also NOT fully hoisted (TDZ)

// const p = new Person(); ← ReferenceError

class Person { constructor(n) { this.name = n; } }

const p = new Person("Sara");

console.log(p.name);   // Sara ✅
▶ Output
Hello! I was hoisted! πŸ‘‹ undefined 100 Hi from arrow! πŸš€ Sara
πŸ’€

Temporal Dead Zone (TDZ) — The Safe Guard for let and const

The Temporal Dead Zone (TDZ) is the region of code between the start of a block scope and the actual declaration line of a let or const variable. During this zone, the variable exists in memory (it was hoisted) but is not initialized — any attempt to access it throws a ReferenceError: Cannot access before initialization.

The TDZ is intentionally designed as a safety feature. It prevents the confusing behavior of var (which silently returns undefined when accessed before assignment). With let and const, JavaScript throws a clear error, telling you exactly what went wrong — making your bugs much easier to find and fix.

// var — NO TDZ, returns undefined (misleading but no error)

console.log(a);   // undefined — no error, just confusing

var a = 5;

console.log(a);   // 5

// let — TDZ active from block start to declaration line

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

let b = 10;

console.log(b);   // ✅ 10 — safe after declaration

// const — same TDZ protection as let

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

const PI = 3.14159;

console.log(PI);  // ✅ 3.14159

// TDZ visual — inside a block

{

  // ← TDZ for 'city' starts here (variable exists in memory but not usable)

  // console.log(city); ← ReferenceError — still in TDZ!

  let city = "Lahore";

  // ← TDZ ends here — city is now initialized

  console.log(city);  // ✅ "Lahore"

}

// TDZ in a function

function testTDZ() {

  // console.log(x); ← ReferenceError — TDZ for x

  let x = 42;

  console.log(x);    // ✅ 42

}

testTDZ();
▶ Output
undefined 5 10 3.14159 Lahore 42
πŸ’‘ TDZ is your friend: When var silently returns undefined, you have no idea something went wrong. When let/const throw a ReferenceError in the TDZ, you immediately know there is a problem and exactly where it is. The TDZ catches bugs early and makes debugging much easier.
⚙️

Execution Context — How JavaScript Prepares and Runs Code

An execution context is the environment in which JavaScript code is evaluated and executed. Every time JavaScript runs any piece of code, it creates an execution context for it. Understanding execution context explains why hoisting works the way it does.

There are two types of execution contexts: the Global Execution Context (created once when your script loads — this is where all global variables live) and a Function Execution Context (created every single time a function is called — it has its own local variables and its own this).

Each execution context goes through two phases. In the Creation Phase, JavaScript scans the code, allocates memory for all variables (var set to undefined, let/const placed in TDZ), and stores complete function declarations. In the Execution Phase, the code actually runs line by line and variables get assigned their real values.

🌍 Global Execution Context
Created once at startup. Gives access to the global object (window in browser). All global variables live here throughout the program's lifetime.
⚙️ Function Execution Context
New context created each time a function is called. Has its own scope, its own this, and its own local variables. Destroyed when function finishes.
πŸ“‹ Execution Context — Two Phases in Order:
Phase 1 (Creation): Allocate var as undefined · let/const go to TDZ · Store full function declarations
Phase 2 (Execution): Run code line by line · Assign real values to variables · Call functions (creates new context)
⚠️

Common Scope & Hoisting Mistakes — And How to Avoid Them

❌ Using var in blocks
var leaks from if/for blocks, causing values to persist outside where you expect them. Always use let or const inside blocks.
❌ Relying on function hoisting
While function declarations hoist, arrow functions don't. Calling an arrow function before its definition throws a TypeError — always define first.
❌ Reading var before assignment
var returns undefined before its assignment line — no error thrown! This silently produces wrong results. Use let/const to get a clear error instead.
❌ Too many global variables
Global variables can be overwritten by any function anywhere. Wrap code in functions or modules to keep variables local and prevent conflicts.

πŸ’Ό Top Interview Questions — Scope & Hoisting in JavaScript

Q1: What are the three types of scope in JavaScript?→ Global scope (everywhere), Local/Function scope (inside a function), Block scope (inside { } with let/const)
Q2: What is hoisting in JavaScript?→ JavaScript moves declarations to the top of their scope before execution. Functions hoist fully. var hoists as undefined. let/const hoist into TDZ.
Q3: What is the Temporal Dead Zone?→ The period between block start and the let/const declaration line where accessing the variable throws ReferenceError.
Q4: Why does var leak from blocks but let does not?→ var is function-scoped — only function boundaries contain it. let/const are block-scoped — any { } boundary contains them.
Q5: What is an execution context and what are its phases?→ The environment where JS code runs. Phase 1 (Creation): hoisting happens. Phase 2 (Execution): code runs line by line.

✅ Complete Summary — Scope & Hoisting in JavaScript

🌍 Global Scope: Declared outside everything — accessible everywhere in the program
⚙️ Local Scope: Declared inside a function — private to that function only
πŸ“¦ Block Scope: let/const inside { } — stays inside, never leaks out like var does
🟑 var scope: Function scoped — leaks from blocks, hoisted as undefined, avoid in 2026
πŸ”΅ let/const scope: Block scoped — contained, TDZ before declaration, preferred always
πŸ—️ Hoisting: Declarations move to top before execution — only declaration, not value
πŸ’€ TDZ: let/const inaccessible before declaration — ReferenceError protects you
Execution Context: Creation phase (hoisting) → Execution phase (code runs) — explains everything
πŸ“Œ Important Note

Scope and hoisting are top interview topics for every JavaScript job. The most practical rules to remember: always declare variables before using them, always use const by default and let when the value changes, never use var, and remember that only function declarations — not arrow functions or function expressions — are fully hoisted. Master these rules and you will avoid 95% of all scope-related bugs!

Comments