Skip to main content

Scope, Closures & Hoisting


Types of Scope

// 1. Global scope
var globalVar = "I'm global";
let globalLet = "Also global";

// 2. Function scope
function outer() {
var funcVar = "function scoped";
// funcVar accessible here
}
// funcVar NOT accessible here

// 3. Block scope (let/const only)
{
let blockLet = "block scoped";
const blockConst = "also block";
var blockVar = "NOT block scoped!"; // var ignores blocks!
}
// blockLet and blockConst NOT accessible here
// blockVar IS accessible here (var leaks!)

// 4. Module scope (when using ES modules)
// Variables are file-scoped, not global

Lexical Scope (Scope Chain)

Inner functions can access variables from outer functions.

const x = "global";

function outer() {
const x = "outer";

function inner() {
const x = "inner";
console.log(x); // "inner" (closest scope wins)
}

function innerNoX() {
console.log(x); // "outer" (finds in parent scope)
}

inner(); // "inner"
innerNoX(); // "outer"
}

console.log(x); // "global"

Hoisting

// var is hoisted with value undefined
console.log(a); // undefined (NOT ReferenceError!)
var a = 10;
console.log(a); // 10

// let and const are hoisted but in Temporal Dead Zone (TDZ)
console.log(b); // ReferenceError: Cannot access before initialization
let b = 20;

// Function declarations are fully hoisted
greet(); // works!
function greet() { console.log("Hello"); }

// Function expressions are NOT hoisted
sayHi(); // TypeError: sayHi is not a function
var sayHi = function() { console.log("Hi"); };

Closures

A closure is a function that remembers variables from its creation scope even after that scope has finished executing.

function makeCounter() {
let count = 0; // "closed over" variable

return function() {
count++;
return count;
};
}

const counter1 = makeCounter();
const counter2 = makeCounter();

counter1(); // 1
counter1(); // 2
counter1(); // 3
counter2(); // 1 (independent closure!)

Practical Closure Use Cases

// 1. Data privacy / encapsulation
function createBankAccount(initial) {
let balance = initial; // private!

return {
deposit(amount) { balance += amount; },
withdraw(amount) { balance -= amount; },
getBalance() { return balance; }
};
}
const account = createBankAccount(100);
account.deposit(50);
account.getBalance(); // 150
// account.balance — undefined, can't access directly!

// 2. Function factory
function makeMultiplier(factor) {
return (n) => n * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5); // 10
triple(5); // 15

// 3. Memoization
function memoize(fn) {
const cache = {};
return function(n) {
if (n in cache) return cache[n];
return cache[n] = fn(n);
};
}

Classic Closure Gotcha (var in loops)

// PROBLEM: All closures share the SAME `i` (var is function-scoped)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 (not 0, 1, 2!)

// SOLUTION 1: Use let (block scope per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2 ✅

// SOLUTION 2: IIFE to create new scope
for (var i = 0; i < 3; i++) {
((j) => setTimeout(() => console.log(j), 100))(i);
}
// Logs: 0, 1, 2 ✅

Variable Shadowing

let name = "Global";

function greet() {
let name = "Local"; // shadows outer name
console.log(name); // "Local"
}

greet(); // "Local"
console.log(name); // "Global" (outer unchanged)

MCQ — Scope, Closures & Hoisting

Q1: What is a closure?

A) A function with no parameters B) A function that has access to variables from its outer scope even after that scope has returned ✅ C) A sealed/frozen object D) An immediately invoked function

Answer: B

Q2: What is the TDZ (Temporal Dead Zone)?

A) The time between declaring and assigning a var variable B) A zone where let/const variables exist but cannot be accessed before their declaration line ✅ C) An area of memory reserved for temporary objects D) The timeout period in async operations

Answer: B

Q3: What does var hoisting do?

A) Moves the declaration AND initialization to the top B) Moves only the declaration to the top (initialized as undefined) ✅ C) Does nothing D) Causes a ReferenceError

Answer: B

Q4: What is the output?
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}

A) 0, 1, 2 B) 3, 3, 3C) undefined D) 1, 2, 3

Answer: B — var is function-scoped, all closures share the same i which becomes 3.

Q5: How do you fix the above loop to log 0, 1, 2?

A) Change var to letB) Use async/await C) Remove the arrow function D) Use clearTimeout

Answer: Alet creates a new binding per iteration.

Q6: Can inner functions access outer function variables?

A) Only if passed as parameters B) Yes, through the scope chain (lexical scope) ✅ C) Only global variables

Answer: B

Q7: What is the output?
console.log(foo());
function foo() { return 42; }

A) undefined B) ReferenceError C) 42D) TypeError

Answer: C — Function declarations are fully hoisted.