Ad – 728Γ—90
βš™οΈ Functions

JavaScript Scope – Global, Function, Block, and Lexical Scope

Scope determines where a variable is visible and accessible in your code. Understanding scope is essential for writing bug-free JavaScript β€” it explains why some variables are accessible and others throw ReferenceErrors, and why the same variable name can mean different things in different places.

⏱️ 20 min read 🎯 Beginner πŸ“… Updated 2026

Global Scope

Variables declared outside any function or block live in the global scope. In a browser, globals are properties of the window object.

JavaScript
// Global variable β€” accessible anywhere in the file
const appName = "ylearner";
let userCount = 0;

function incrementUsers() {
  userCount++; // Accesses and modifies global variable
}

incrementUsers();
incrementUsers();
console.log(userCount); // 2
console.log(appName);   // ylearner
β–Ά Output
2 ylearner
⚠️
Avoid Global Scope Pollution

Every global variable can be accidentally overwritten by any other script or library. Keep globals minimal. In modules (type="module"), variables are module-scoped by default, not global.

Function (Local) Scope

Variables declared inside a function are scoped to that function β€” invisible outside it.

JavaScript
function calculateTax(price) {
  const taxRate = 0.15; // Local β€” only visible inside calculateTax
  const tax = price * taxRate;
  return tax;
}

console.log(calculateTax(100)); // 15

try {
  console.log(taxRate); // ReferenceError!
} catch (e) {
  console.log("Error: taxRate is not accessible outside the function.");
}

// Each function call gets its own scope
function counter() {
  let count = 0; // Fresh for each call
  count++;
  return count;
}
console.log(counter()); // 1
console.log(counter()); // 1 β€” fresh count each time
β–Ά Output
15 Error: taxRate is not accessible outside the function. 1 1

Block Scope (let and const)

let and const are block-scoped β€” they live only inside the {} block where they are declared.

JavaScript
// Block scope with if statement
if (true) {
  let blockVar = "I'm in the if block";
  const blockConst = "Me too";
  console.log(blockVar);   // Works
}
// blockVar is gone here
try {
  console.log(blockVar);
} catch (e) {
  console.log("blockVar not accessible outside block");
}

// Block scope with for loops
for (let i = 0; i < 3; i++) {
  const msg = `iteration ${i}`;
  console.log(msg);
}
// i and msg are not accessible here
β–Ά Output
I'm in the if block blockVar not accessible outside block iteration 0 iteration 1 iteration 2

var and Function Scope Gotcha

var ignores block scope β€” it is function-scoped. This leads to surprising behavior with loops and conditionals.

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

// var in a for loop leaks into the function scope
for (var j = 0; j < 3; j++) { /* ... */ }
console.log(j); // 3 β€” j is still accessible!

// let is properly block-scoped
for (let k = 0; k < 3; k++) { /* ... */ }
try {
  console.log(k); // ReferenceError
} catch (e) {
  console.log("k not accessible outside loop");
}
β–Ά Output
I escaped the if block! 3 k not accessible outside loop

Scope Chain

When JavaScript looks up a variable, it starts in the current scope and moves outward through parent scopes until it finds the variable or reaches the global scope.

JavaScript
const globalVar = "global";

function outer() {
  const outerVar = "outer";

  function inner() {
    const innerVar = "inner";

    // Inner can access all parent scopes
    console.log(innerVar);  // "inner"   β€” own scope
    console.log(outerVar);  // "outer"   β€” parent scope
    console.log(globalVar); // "global"  β€” global scope
  }

  inner();

  // Outer cannot access inner's scope
  try {
    console.log(innerVar);
  } catch (e) {
    console.log("innerVar not accessible in outer");
  }
}

outer();
β–Ά Output
inner outer global innerVar not accessible in outer

Lexical Scope

Lexical scope means that scope is determined by where a function is written in the source code, not where it is called from. JavaScript is lexically scoped.

JavaScript
const lang = "JavaScript";

function getLanguage() {
  return lang; // Reads 'lang' from where getLanguage is defined (global)
}

function demo() {
  const lang = "Python"; // Local shadowing variable
  console.log(getLanguage()); // "JavaScript" β€” NOT "Python"!
  // getLanguage sees the scope where it was defined, not where it's called
}

demo();
β–Ά Output
JavaScript
ℹ️
Lexical vs Dynamic Scope

Some older languages use dynamic scope β€” a function sees the variables of its caller. JavaScript (like most modern languages) uses lexical scope β€” a function sees the variables of its definition site. This is why closures work.

Variable Shadowing

A variable in an inner scope with the same name as an outer one shadows the outer variable within that scope.

JavaScript
const x = 10; // Outer x

function example() {
  const x = 20; // Shadows outer x inside this function
  console.log(x); // 20 β€” inner x
}

example();
console.log(x); // 10 β€” outer x unchanged

// Block-level shadowing
let count = 0;
if (true) {
  let count = 100; // Shadows outer count in this block
  console.log(count); // 100
}
console.log(count); // 0 β€” outer count unchanged
β–Ά Output
20 10 100 0

Scope in Loops – The Classic var Bug

JavaScript
// Bug: var creates one shared variable for all iterations
const funcsVar = [];
for (var i = 0; i < 3; i++) {
  funcsVar.push(() => i);
}
console.log(funcsVar[0]()); // 3 β€” not 0!
console.log(funcsVar[1]()); // 3
console.log(funcsVar[2]()); // 3

// Fix: let creates a new binding per iteration
const funcsLet = [];
for (let j = 0; j < 3; j++) {
  funcsLet.push(() => j);
}
console.log(funcsLet[0]()); // 0
console.log(funcsLet[1]()); // 1
console.log(funcsLet[2]()); // 2
β–Ά Output
3 3 3 0 1 2

IIFE for Scope Isolation

JavaScript
// Before ES modules, IIFEs isolated library code
const MyLibrary = (function() {
  // Private variables β€” not accessible outside
  let privateCounter = 0;
  const privateConfig = { version: "1.0" };

  function increment() {
    privateCounter++;
  }

  // Public API
  return {
    getCount: () => privateCounter,
    bump: () => { increment(); return MyLibrary; },
    version: privateConfig.version
  };
})();

MyLibrary.bump().bump().bump();
console.log(MyLibrary.getCount()); // 3
console.log(MyLibrary.version);    // 1.0
β–Ά Output
3 1.0
Ad – 336Γ—280

πŸ‹οΈ Practical Exercise

Without running the code, predict the output of each console.log and explain why:

JavaScript
var a = 1;
let b = 2;

function test() {
  var a = 10;
  if (true) {
    var a = 20;  // var β€” function scoped
    let b = 30;  // let β€” block scoped
    console.log(a); // A
    console.log(b); // B
  }
  console.log(a); // C
  console.log(b); // D
}
test();
console.log(a); // E
console.log(b); // F

πŸ”₯ Challenge Exercise

Build a createNamespace(name) function using an IIFE that returns an object with set(key, value), get(key), and list() methods. The internal storage must be private (not accessible from outside). Create two separate namespaces and show they don't interfere with each other.

πŸ“‹ Summary

  • Global scope: accessible everywhere β€” minimize globals to avoid collisions.
  • Function scope: var and function parameters β€” only visible inside the function.
  • Block scope: let/const β€” restricted to the {} block they're declared in.
  • Scope chain: inner scopes can read outer variables; outer cannot read inner.
  • Lexical scope: where a function is defined β€” not called β€” determines its variable access.
  • Variable shadowing: same name in inner scope hides the outer one.
  • Always prefer let/const over var to avoid scope leaks.

Interview Questions

  • What is the difference between function scope and block scope?
  • Why does var cause problems in for loops with closures? How does let fix it?
  • Explain the scope chain with an example of nested functions.
  • What is lexical scope? How does it differ from dynamic scope?
  • What is variable shadowing and when can it cause bugs?

Frequently Asked Questions

What is the Temporal Dead Zone (TDZ)?+

The TDZ is the period from when a let or const binding is created (hoisted) to when it is initialized. Accessing the variable during TDZ throws a ReferenceError. This is why let/const can't be used before their declaration line, unlike var (which initializes to undefined).

What is module scope?+

JavaScript modules (<script type="module"> or ESM files) have their own scope. Variables declared at the top of a module are module-scoped β€” they don't become global even without let/const/var. This eliminates the global pollution problem.

Can two functions have variables with the same name?+

Yes, completely fine. Each function has its own scope. function a() { const x = 1; } and function b() { const x = 2; } have independent x variables that never conflict.