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
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); // ❌ ReferenceErrorPart 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
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
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 parentPart 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
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 siteClosures — lexical scope in practice
A closure is when a function remembers the variables from its outer scope, even after the outer function has returned.
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.