A closure is a function that has access to the parent scope, even after the parent function has closed. A closure is formed when a function remembers the variables fro its lexical scope even when the function is executed outside its lexical scope.
function outerFunction() {
let outerVariable = 'I am from outer!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const closure1 = outerFunction();
closure1(); // Output: I am from outer!
Explanation:
innerFunction forms a closure over outerVariable from outerFunction.outerFunction finishes, innerFunction can access outerVariable.function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3
Explanation:
count is private to the returned function, not accessible from outside.counter() increments and returns the private count.Problem:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() {
console.log('Problem:', i);
});
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3
Solution 1: IIFE
var funcsFixed = [];
for (var j = 0; j < 3; j++) {
(function(jCopy) {
funcsFixed.push(function() {
console.log('Fixed:', jCopy);
});
})(j);
}
funcsFixed[0](); // 0
funcsFixed[1](); // 1
funcsFixed[2](); // 2
Solution 2: Use let
let funcsLet = [];
for (let k = 0; k < 3; k++) {
funcsLet.push(function() {
console.log('Let:', k);
});
}
funcsLet[0](); // 0
funcsLet[1](); // 1
funcsLet[2](); // 2
Explanation:
var causes all functions to share the same variable.let creates a new scope for each iteration.function makeMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
double(5); // 10
triple(5); // 15
Explanation:
makeMultiplier returns a function that remembers the multiplier value.Problem:
for (var m = 0; m < 3; m++) {
setTimeout(function() {
console.log('Async Problem:', m);
}, 100);
}
// All log 3
Solution:
for (let n = 0; n < 3; n++) {
setTimeout(function() {
console.log('Async Fixed:', n);
}, 200);
}
// Logs 0, 1, 2
Explanation:
let creates a new binding for each iteration, so closures capture the correct value.function Person(name) {
let _name = name;
return {
getName: function() { return _name; },
setName: function(newName) { _name = newName; }
};
}
const p = Person('Alice');
p.getName(); // Alice
p.setName('Bob');
p.getName(); // Bob
Explanation:
_name is private, only accessible via getName and setName.A closure is a function that remembers its lexical scope even when the function is executed outside that scope. It allows functions to access variables from an enclosing scope, even after that scope has closed.
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
add5(10); // 15
Explanation:
add returns a function that remembers a.Be careful: Closures can cause memory leaks if they retain references to large objects unnecessarily. Always clean up references if not needed.
Example:
function createLeak() {
let largeArray = new Array(1e6).fill('leak'); // Large object
return function() {
// This closure keeps largeArray in memory
console.log('Still holding largeArray of length:', largeArray.length);
};
}
let leaky = createLeak();
// Even if we don't need largeArray anymore, it's not garbage collected
// because the closure (leaky) still references it.
// To avoid the leak, set leaky = null when done:
// leaky = null;
Closures are a powerful feature in JavaScript, enabling data privacy, function factories, and more. Understanding closures is essential for interviews and real-world development.
This section explains how closures interact with setTimeout and loops, a common source of confusion in JavaScript.
var in Loopsfunction y() {
for (var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
y();
What happens?
6 five times (after 1s, 2s, …, 5s).var is function-scoped, not block-scoped. By the time the callbacks run, the loop is done and i is 6.function z() {
for (var i = 1; i <= 5; i++) {
(function(closeI) {
setTimeout(() => {
console.log(closeI);
}, closeI * 1000);
})(i);
}
}
z();
How does this work?
i as closeI.1, 2, 3, 4, 5 as expected.letfunction a() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
a();
How does this work?
let is block-scoped, so each iteration gets a new binding of i.i value.0, 1, 2, 3, 4 as expected.var, all callbacks share the same variable, leading to unexpected results in async code.let or an IIFE to capture the correct value for each iteration.Summary:
When using asynchronous functions like
setTimeoutinside loops, always be mindful of variable scope. Useletor an IIFE to avoid common pitfalls with closures andvar.
Practice: Try modifying the examples in closures.js & setTimeOutPractice.js and observe the results to deepen your understanding.




Scope defines the region of the program where an identifier (variable, function, class) can be accessed.
JavaScript uses lexical (static) scoping, meaning:
cope in JavaScript (Foundational Model)
Scope = where a variable is accessible in code. JavaScript scope is lexical (static), determined at parse time, not runtime.
1.1 Types of Scope
| Scope Type | Introduced By | Visibility |
|---|---|---|
| Global Scope | outside all functions | everywhere |
| Function Scope | function | entire function body |
| Block Scope | {} with let / const | block only |
| Module Scope | ES Modules | file-only |
let g = 10; // global
function f() {
let a = 1; // function scope
if (true) {
let b = 2; // block scope
}
}
Key rule:
var → function scoped
let / const → block scoped
Declared outside all functions / blocks.
let a = 10;
function f() {
console.log(a);
}
Created by function.
function test() {
var x = 5;
let y = 6;
}
x and y inaccessible outsidevar is function-scoped, not block-scopedCreated by {} only with let and const.
if (true) {
let a = 1;
const b = 2;
}
a, b exist only inside block// file.js
const secret = 42;
export {};
A function can access variables from its own scope and all parent scopes, determined by where it is written.
let x = 100;
function outer() {
let y = 200;
function inner() {
console.log(x, y);
}
inner();
}
inner can access y and xLexical scope is one-directional (inside → outside).
When JavaScript resolves a variable:
ReferenceErrorlet a = 1;
function f() {
let b = 2;
function g() {
let c = 3;
console.log(a, b, c);
}
g();
}
Scope chain:
g → f → global
Each function invocation creates an Execution Context containing:
var, function declarations)let, const)Execution Contexts are pushed onto the Call Stack.
A closure is a function that retains access to variables from its lexical environment even after the outer function has finished execution.
Closures are:
Closures exist because:
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
outer() invokedcount created in lexical environmentinner returnedouter removedcount moved to heapinner holds reference to countcounter(); // 1
counter(); // 2
function test() {
let x = 10;
return () => console.log(x);
}
const fn = test();
x = 100; // unrelated
fn(); // 10
The closure references its own lexical environment, not global changes.
function createCounter() {
let c = 0;
return () => ++c;
}
const c1 = createCounter();
const c2 = createCounter();
c1(); // 1
c1(); // 2
c2(); // 1
Each invocation creates a new lexical environment.
function bankAccount(balance) {
return {
deposit(x) { balance += x; },
getBalance() { return balance; }
};
}
balance cannot be accessed directlyfunction multiplyBy(n) {
return x => x * n;
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
function fetchData() {
let token = "auth123";
setTimeout(() => {
console.log(token);
}, 1000);
}
Async does not break closure.
var in Loopsfor (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
Output:
3
3
3
var is function-scopedifor (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
| Aspect | Scope | Closure |
|---|---|---|
| What | Accessibility | Lifetime |
| Exists | Always | Only when referenced |
| Memory | Stack-based | Heap-retained |
| Purpose | Name resolution | State preservation |
Closures can prevent garbage collection.
function heavy() {
let large = new Array(1e6);
return () => large.length;
}
As long as closure exists:
large remains in memoryMitigation:
large = null;
Closures are used in:
useState is closure-based)function memoize(fn) {
const cache = {};
return function(n) {
if (cache[n]) return cache[n];
return cache[n] = fn(n);
};
}
Scope defines visibility. Closure defines persistence.
let/const avoid shared reference bugsfunction bankAccount(balance) {
return {
deposit(x) { balance += x; },
getBalance() { return balance; }
};
}
balance cannot be accessed directly