Scopes & Closures
Variable scope, hoisting, closures, module pattern
What you'll learn
Understand scope types (global, function, block)
Explain hoisting and temporal dead zone
Use closures for private state
Fix the loop + closure gotcha
Scope Types
const g = "global"
function outer() {
const f = "function scope"
if (true) {
let b = "block scope"
var v = "NOT block" // var ignores blocks
}
console.log(v) // "NOT block"
}
Gotcha
var is function-scoped. Use let/const.
Hoisting & TDZ
console.log(a) // undefined (hoisted)
// console.log(b) // ReferenceError: TDZ
var a = 1
let b = 2 // hoisted but uninitialized until this line
Closures
A closure remembers its lexical scope even when called elsewhere.
function createCounter() {
let count = 0
return { increment: () => ++count, getCount: () => count }
}
const c = createCounter()
c.increment() // 1
// No direct access to count
// Private state
function createBankAccount(initial) {
let balance = initial
return {
deposit: (amt) => {
balance += amt
return balance
},
getBalance: () => balance,
}
}
// Memoization
function memoize(fn) {
const cache = new Map()
return (arg) =>
cache.has(arg) ? cache.get(arg) : (cache.set(arg, fn(arg)), cache.get(arg))
}
Loop + Closure Gotcha
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i), 0) // 3, 3, 3 (shared binding)
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i), 0) // 0, 1, 2 (per-iteration binding)
See It In Action
Build a Private Counter
Code
javascript
1function createAdvancedCounter(initial = 0) {2let count = initial3return {4 increment: (step = 1) => {5 if (step <= 0) throw new Error("Step must be positive")6 count += step7 return count8 },9 decrement: (step = 1) => {10 if (step <= 0) throw new Error("Step must be positive")11 count -= step12 return count13 },14 reset: () => { count = initial; return count },15 getCount: () => count,16}17}18 19const counter = createAdvancedCounter(10)20console.log(counter.increment(5)) // 1521console.log(counter.decrement(3)) // 1222console.log(counter.getCount()) // 1223console.log(counter.reset()) // 10A closure encapsulates `count`— the returned methods close over the variable, making it private. No external code can modify`count`except through`increment`, `decrement`, or `reset`.
Lesson Summary
Key Takeaways
Global, function (var), and block (let/const) scope
let/const have TDZ — access before declaration throws
Closures enable private state and memoization
var in loops shares binding; let creates per-iteration bindings