DocsHub
Functions

Scope

Learn how JavaScript decides where variables are accessible using global, function, and block scope.

Scope

When you create a variable, it is not automatically available everywhere in your code. Scope determines where a variable can be accessed and where it cannot.

Think of scope like rooms in a house. A variable created in the bedroom is only accessible in the bedroom. But a variable in the hallway — the shared space — is accessible from every room.

let hallway = "I am accessible everywhere";

function bedroom() {
  let private = "Only accessible in here";
  console.log(hallway);  // ✅ works — hallway is in outer scope
  console.log(private);  // ✅ works — we are inside bedroom
}

bedroom();
console.log(private); // ❌ ReferenceError — private is not accessible out here

JavaScript has three types of scope — global, function, and block.


The Three Scopes

Global Scope Function Scope Block Scope Global variablesaccessible everywhere Function variablesonly inside function Block variablesonly inside block

1. Global Scope

A variable declared outside of any function or block lives in the global scope. It is accessible from anywhere in your code.

const siteName = "DocsHub";
let visitorCount = 0;

function showSite() {
  console.log(siteName);    // ✅ accessible
  visitorCount++;           // ✅ accessible
}

function logVisit() {
  console.log(`Visitors: ${visitorCount}`); // ✅ accessible
}

showSite();
logVisit(); // Visitors: 1

Both functions can read and update visitorCount because it lives in the global scope.

Avoid creating too many global variables. Every piece of your code can accidentally overwrite them — and in large projects this causes hard-to-find bugs. Keep variables as local as possible.


2. Function Scope

Variables declared inside a function are only accessible inside that function. They are created when the function runs and destroyed when it finishes.

function calculateTax() {
  const taxRate = 0.17;     // only exists inside calculateTax
  let amount = 1000;
  console.log(amount * taxRate); // 170
}

calculateTax();

console.log(taxRate); // ❌ ReferenceError — taxRate does not exist out here
console.log(amount);  // ❌ ReferenceError — amount does not exist out here

Each function call gets its own fresh scope. Variables from one call do not leak into another.

function greet(name) {
  let message = `Hello, ${name}!`; // fresh every call
  console.log(message);
}

greet("Ali");  // Hello, Ali!
greet("Sara"); // Hello, Sara!

message is created fresh on every call and thrown away when the function ends.


3. Block Scope

A block is any code inside curly braces {} — an if statement, a loop, or just a standalone block. Variables declared with let and const inside a block are only accessible within that block.

if (true) {
  let blockVar = "I am block scoped";
  const alsoBlock = "Me too";
  console.log(blockVar);  // ✅ works
}

console.log(blockVar);  // ❌ ReferenceError
console.log(alsoBlock); // ❌ ReferenceError

This is exactly why var was problematic — it ignores block scope:

if (true) {
  var leaked = "I escape the block";
  let contained = "I stay here";
}

console.log(leaked);    // ✅ "I escape the block" — var leaks out
console.log(contained); // ❌ ReferenceError — let stays contained

This is one of the main reasons var was replaced by let and const.


Nested Scope — Scope Chain

Scopes can be nested inside each other. When JavaScript looks for a variable, it starts in the current scope and works its way outward until it finds it — or throws an error.

This outward lookup is called the scope chain.

Yes No Yes No Yes No Current Scope Variable Found? Use Variable Outer Scope Found There? Continue Up Scope Chain Global Scope Reached? ReferenceError
const global = "global";

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

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

    // inner can see all three — its own, outer's, and global
    console.log(innerVar);  // ✅ "inner"
    console.log(outerVar);  // ✅ "outer" — found in outer scope
    console.log(global);    // ✅ "global" — found in global scope
  }

  // outer can see its own and global — but not inner's
  console.log(outerVar);  // ✅ "outer"
  console.log(global);    // ✅ "global"
  console.log(innerVar);  // ❌ ReferenceError — inner is not accessible up here

  inner();
}

outer();

The lookup only goes outward — never inward. Inner functions can see outer variables, but outer functions cannot see inner variables.


Variable Shadowing

When a variable in an inner scope has the same name as one in an outer scope, the inner one shadows the outer one — it takes priority inside that scope.

const language = "Python";

function printLanguage() {
  const language = "JavaScript"; // shadows the outer language
  console.log(language);         // "JavaScript" — inner wins
}

printLanguage();
console.log(language); // "Python" — outer is unchanged

The outer language is not changed — it is simply hidden inside printLanguage. Once you leave the function, the outer one is visible again.

Shadowing is not a bug — but accidental shadowing can cause confusion. Be intentional with variable names.


Lexical Scope

JavaScript uses lexical scope — meaning scope is determined by where you write the code, not where you run it.

const name = "Ali";

function printName() {
  console.log(name); // looks up in the scope where printName was WRITTEN
}

function callPrint() {
  const name = "Sara"; // this does not affect printName
  printName();
}

callPrint(); // "Ali" — not "Sara"

printName was written in the global scope — so it looks up name in the global scope, even when called from inside callPrint. Where the function is called from does not matter — only where it was defined.

This is the foundation of one of the most important JavaScript concepts — closures — which are covered in the Advanced section.


A Real Example — Score Tracker

let highScore = 0; // global — shared across the game

function playRound(playerName) {
  let score = 0; // local to this round

  // simulate scoring
  for (let i = 0; i < 3; i++) { // i is block scoped to this loop
    let points = Math.floor(Math.random() * 10) + 1;
    score += points;
  }

  console.log(`${playerName} scored: ${score}`);

  if (score > highScore) {
    highScore = score; // update the global high score
    console.log(`New high score: ${highScore}!`);
  }
}

playRound("Ali");
playRound("Sara");
playRound("Zara");
console.log(`Final high score: ${highScore}`);
  • highScore — global, shared and updated across all rounds
  • score — function scoped, fresh for every round
  • points and i — block scoped, exist only inside the loop

Each variable lives at exactly the right level of scope for its purpose.


Summary

  • Scope determines where a variable is accessible in your code
  • Global scope — accessible everywhere, declared outside all functions and blocks
  • Function scope — accessible only inside the function where it was declared
  • Block scope — accessible only inside the {} block — only let and const respect block scope, var does not
  • Scope chain — when a variable is not found in the current scope, JavaScript looks outward until it finds it or throws a ReferenceError
  • Variable shadowing — an inner variable with the same name as an outer one takes priority inside its scope
  • Lexical scope — scope is determined by where the code is written, not where it is called from
  • Keep variables as local as possible — avoid polluting the global scope

On this page