Manifest Stack home

Scope in JS

Part 1: The Three Types of Scope

Global Scope

Variables declared outside any function or block. Accessible everywhere in the program. Use sparingly — too many globals cause bugs.

Function Scope

Variables declared inside a function. Only accessible within that function and its nested functions. Created when the function is called, destroyed when it returns.

Block Scope

Variables declared with let or const inside {} — only accessible within that block. var ignores blocks.

All three scopes in one example

javascript
let globalVar = "I'm global";       // Global scope

function myFunc() {
    let funcVar = "I'm function-scoped"; // Function scope

    if (true) {
        let blockVar = "I'm block-scoped";  // Block scope
        var varVar   = "I ignore blocks";   // Function scope (var!)

        console.log(globalVar); // ✅
        console.log(funcVar);   // ✅
        console.log(blockVar);  // ✅
    }

    console.log(varVar);    // ✅ var leaks out of block!
    console.log(blockVar);  // ❌ ReferenceError
}

myFunc();
console.log(funcVar);   // ❌ ReferenceError

Part 2: The Scope Chain

When JavaScript looks up a variable, it searches the current scope first. If not found, it moves up to the outer scope. It keeps going up until it reaches the global scope. If still not found — ReferenceError. This chain of scopes is called the scope chain.

Scope chain lookup

javascript
let a = "global";

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

    function inner() {
        let c = "inner";

        console.log(c); // ✅ found in inner scope
        console.log(b); // ✅ not in inner → look in outer → found!
        console.log(a); // ✅ not in inner → not in outer → look in global → found!
        console.log(d); // ❌ not found anywhere → ReferenceError
    }

    inner();
}

outer();

Inner scopes can see outer variables, but NOT the reverse

javascript
function parent() {
    let parentVar = "I'm in parent";

    function child() {
        console.log(parentVar); // ✅ child can see parent's variables
    }

    child();
}

parent();
console.log(parentVar); // ❌ ReferenceError — global can't see inside parent

Part 3: Lexical Scope

JavaScript uses lexical scope (also called static scope) — a function's scope is determined by where it is written in the code, not where it is called from. This is the basis of closures.

Scope is determined by where the function is defined

javascript
let x = "global";

function define() {
    let x = "inside define";

    function inner() {
        console.log(x); // "inside define" — lexically scoped to 'define'
    }

    return inner;
}

function caller() {
    let x = "inside caller";
    let fn = define();
    fn(); // logs "inside define", NOT "inside caller" or "global"
}

caller();
// inner() looks up x in its DEFINITION environment, not its call site

Closures — lexical scope in practice

A closure is when a function remembers the variables from its outer scope, even after the outer function has returned.

javascript
function makeCounter() {
    let count = 0;          // in makeCounter's scope

    return function() {
        count++;            // inner function remembers 'count'
        return count;
    };
}

const counter = makeCounter(); // makeCounter returns, but count survives
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 'count' is not garbage collected because 'counter' still has
// a reference to it through the closure.