Scope & Closures
Local vs global scope and closures
Scope in JavaScript
Scope determines where variables are accessible in your code. Understanding scope is crucial for avoiding bugs and writing clean code.
Types of Scope
Global Scope
Variables declared outside any function or block are global—accessible everywhere:
const globalVar = "I'm global";
function showGlobal() {
console.log(globalVar); // Accessible here
}
showGlobal(); // "I'm global"
console.log(globalVar); // "I'm global"
Function Scope
Variables declared inside a function are only accessible within that function:
function myFunction() {
const functionVar = "I'm local to this function";
console.log(functionVar); // Works
}
myFunction();
// console.log(functionVar); // Error: functionVar is not defined
Block Scope
let and const are block-scoped (confined to {}):
if (true) {
const blockVar = "I'm block-scoped";
let anotherBlock = "Me too";
console.log(blockVar); // Works here
}
// console.log(blockVar); // Error: blockVar is not defined
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
// console.log(i); // Error: i is not defined
Scope Chain
JavaScript looks for variables from innermost to outermost scope:
const global = "global";
function outer() {
const outerVar = "outer";
function inner() {
const innerVar = "inner";
// Can access all three
console.log(innerVar); // "inner"
console.log(outerVar); // "outer"
console.log(global); // "global"
}
inner();
// console.log(innerVar); // Error: can't access inner scope
}
outer();
Closures
A closure is a function that remembers variables from its outer scope, even after that scope has executed:
function createCounter() {
let count = 0; // Private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count is private—can't access it directly
// console.log(counter.count); // undefined
Practical Closure Examples
// Example 1: Private data
function bankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return "Insufficient funds";
},
getBalance() {
return balance;
}
};
}
const myAccount = bankAccount(1000);
console.log(myAccount.deposit(500)); // 1500
console.log(myAccount.withdraw(200)); // 1300
console.log(myAccount.getBalance()); // 1300
// Example 2: Function factory
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
⚠️ Common Pitfalls
Closures in loops can be tricky:
// Problem: Using var in a loop
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3 (not 0, 1, 2!)
}, 1000);
}
// Solution 1: Use let (block-scoped)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 1000);
}
// Solution 2: Create a closure with IIFE
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2
}, 1000);
})(i);
}
✓ Best Practices
- • Use
constandletfor block scoping - • Avoid polluting the global scope
- • Use closures to create private variables
- • Be aware of closure memory implications (they keep outer variables alive)
- • Use closures for data encapsulation and module patterns