ScopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it. answers the question: "Where can I use this variable?" Understanding scope is crucial because it determines the lifetime and visibility of your data. Get scope wrong, and you'll have bugs where variables disappear or unexpectedly share values. Get it right, and you unlock powerful patterns like closures and data privacy.
The three types of scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.
Global scope
Variables declared outside any function or block are in the global scope:
// Global variable
const apiKey = "abc123";
function fetchData() {
// Can access global variables
console.log(apiKey); // "abc123"
}
fetchData();
console.log(apiKey); // "abc123" (accessible everywhere)Global variables are accessible everywhere, which sounds convenient but is actually dangerous. Any part of your code can change them, leading to hard-to-debug issues. Modern best practice is to minimize global variables.
Function scope
Variables declared inside a function only exist inside that function:
function calculateTotal() {
const tax = 0.2; // Only exists in this function
const price = 100;
return price * (1 + tax);
}
console.log(calculateTotal()); // 120
console.log(tax); // ReferenceError: tax is not definedFunctions are the primary way to organize and encapsulate code in JavaScript. Each function creates a new scope.
Block scope
let and const are block-scoped, they only exist within the curly braces where they're defined:
if (true) {
const message = "Hello";
let count = 0;
console.log(message); // "Hello"
}
console.log(message); // ReferenceError
console.log(count); // ReferenceErrorThis includes if statements, loops, and even bare blocks:
{
const secret = "hidden";
}
console.log(secret); // ReferenceError| Scope type | Created by | var behavior | let/const behavior |
|---|---|---|---|
| Global | Top-level code | Accessible everywhere | Accessible everywhere |
| Function | function() {} | Confined to function | Confined to function |
| Block | if, for, {} | Leaks out of block | Confined to block |
The var problem
Before ES6, JavaScript only had var, which is function-scoped (not block-scoped):
if (true) {
var oldStyle = "I'm everywhere in this function!";
}
console.log(oldStyle); // "I'm everywhere in this function!"This "leaks" variables out of blocks and causes confusion. Always use let or const instead.
Lexical scopeWhat is lexical scope?The rule that a variable's visibility is determined by where it is written in the source code, not where it is called from.
JavaScript uses lexical scope (also called static scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it.), which means scope is determined by where functions are written in the code, not where they're called:
const outer = "I'm outside!";
function innerFunc() {
console.log(outer); // Can see outside!
}
function wrapper() {
const outer = "I'm inside wrapper!";
innerFunc(); // Still logs "I'm outside!"
}
innerFunc(); // "I'm outside!"
wrapper(); // "I'm outside!"Even though innerFunc is called from inside wrapper, it was defined in the global scope, so it looks for variables there. This is lexical scope in action.
Closures
A closureWhat is closure?A function that remembers variables from the surrounding code where it was created, even after that surrounding code has finished running. is a function that remembers the variables from its outer scopeWhat is scope?The area of your code where a variable is accessible; variables declared inside a function or block are invisible outside it., even after the outer function has finished executing:
function createCounter() {
let count = 0; // This variable is "closed over"
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2Even though createCounter finished running, the returned functions still have access to count. The variables are "closed over" and preserved.
var in a for loop and push functions that all reference the same variable, so every function returns the final loop value instead of the value at the time it was created. AI also generates stale closures in React: event handlers or useEffect callbacks that capture an old state value and never see updates. Always check whether AI-generated closures capture the right value at the right time.Each closure is independent
Each call to createCounter creates a new, separate count:
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
console.log(counterA.getCount()); // 1
console.log(counterB.getCount()); // 0 (separate!)Practical closure uses
Data privacy:
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private variable
return {
deposit: (amount) => {
balance += amount;
return balance;
},
withdraw: (amount) => {
if (amount > balance) {
throw new Error("Insufficient funds");
}
balance -= amount;
return balance;
},
getBalance: () => balance
};
}
const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
console.log(account.balance); // undefined (can't access directly!)Function factories:
function makeMultiplier(factor) {
return (number) => number * factor;
}
const triple = makeMultiplier(3);
const double = makeMultiplier(2);
console.log(triple(5)); // 15
console.log(double(5)); // 10Quick reference
| Scope type | Variables | Accessible from |
|---|---|---|
| Global | Declared outside functions | Everywhere |
| Function | Declared inside functions | Inside that function |
| Block | let/const in {} | Inside that block |
| Closure pattern | What it does | Example use |
|---|---|---|
| Data hiding | Create private variables | Bank account, counter |
| State preservation | Remember values between calls | Caching, memoization |
| Factory functions | Create customized functions | Multipliers, formatters |
// Closure example
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// Private variables with closure
function createBankAccount(balance) {
return {
deposit: (amount) => balance += amount,
getBalance: () => balance
};
}
const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150