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:
innerFunctionforms a closure overouterVariablefromouterFunction.- Even after
outerFunctionfinishes,innerFunctioncan accessouterVariable.
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
counter(); // 3Explanation:
countis private to the returned function, not accessible from outside.- Each call to
counter()increments and returns the privatecount.
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](); // 3Solution 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](); // 2Solution 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](); // 2Explanation:
- Using
varcauses all functions to share the same variable. - IIFE or
letcreates 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); // 15Explanation:
makeMultiplierreturns a function that remembers themultipliervalue.
Problem:
for (var m = 0; m < 3; m++) {
setTimeout(function() {
console.log('Async Problem:', m);
}, 100);
}
// All log 3Solution:
for (let n = 0; n < 3; n++) {
setTimeout(function() {
console.log('Async Fixed:', n);
}, 200);
}
// Logs 0, 1, 2Explanation:
letcreates 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(); // BobExplanation:
_nameis private, only accessible viagetNameandsetName.- Demonstrates data privacy using closures.
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.
- Data privacy (private variables)
- Function factories
- Partial application/currying
- Event handlers and callbacks
- Maintaining state in async code
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
add5(10); // 15Explanation:
addreturns a function that remembersa.- This is the basis for currying and partial application.
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.
function y() {
for (var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
y();What happens?
- This will log
6five times (after 1s, 2s, ..., 5s). - Why? Because
varis function-scoped, not block-scoped. By the time the callbacks run, the loop is done andiis6.
function z() {
for (var i = 1; i <= 5; i++) {
(function(closeI) {
setTimeout(() => {
console.log(closeI);
}, closeI * 1000);
})(i);
}
}
z();How does this work?
- The IIFE creates a new scope for each iteration, capturing the current value of
iascloseI. - Each timeout callback now has its own copy of the value.
- This logs
1, 2, 3, 4, 5as expected.
function a() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
a();How does this work?
letis block-scoped, so each iteration gets a new binding ofi.- Each callback closes over its own
ivalue. - This logs
0, 1, 2, 3, 4as expected.
- Closures allow the callback to "remember" the variable from its surrounding scope.
- With
var, all callbacks share the same variable, leading to unexpected results in async code. - Use
letor 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:
- Scope is decided at code authoring time
- Not at runtime
- Based on physical nesting in source code
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);
}- Lives for the entire lifetime of the program
- Pollutes namespace if overused
Created by function.
function test() {
var x = 5;
let y = 6;
}xandyinaccessible outsidevaris function-scoped, not block-scoped
Created by {} only with let and const.
if (true) {
let a = 1;
const b = 2;
}a,bexist only inside block- Prevents accidental overwrites
// file.js
const secret = 42;
export {};- Variables are private to file unless exported
- No implicit globals
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();
}innercan accessyandx- Parent cannot access child scope
Lexical scope is one-directional (inside → outside).
When JavaScript resolves a variable:
- Look in current scope
- If not found, go to outer scope
- Continue until global scope
- If still not found →
ReferenceError
let 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:
- Variable Environment (
var, function declarations) - Lexical Environment (
let,const) - Outer Lexical Environment reference
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:
- Automatic
- Engine-level behavior
- Not created explicitly by syntax
Closures exist because:
- JavaScript uses lexical scoping
- Functions are first-class objects
- Returned functions keep references to outer variables
- Garbage collection preserves referenced variables
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();outer()invokedcountcreated in lexical environmentinnerreturned- Call stack frame of
outerremoved countmoved to heapinnerholds reference tocount
counter(); // 1
counter(); // 2function test() {
let x = 10;
return () => console.log(x);
}
const fn = test();
x = 100; // unrelated
fn(); // 10The 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(); // 1Each invocation creates a new lexical environment.
function bankAccount(balance) {
return {
deposit(x) { balance += x; },
getBalance() { return balance; }
};
}balancecannot be accessed directly- True privacy without classes
function 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.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}Output:
3
3
3
varis function-scoped- Single shared
i - Closures capture same reference
for (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:
largeremains in memory
Mitigation:
large = null;Closures are used in:
- Memoization
- Debouncing / Throttling
- Caching layers
- Iterators
- DFS recursion state
- React Hooks (
useStateis 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.
- JavaScript uses lexical scoping
- Scope chain resolves identifiers upward
- Closures retain lexical environment via heap
let/constavoid shared reference bugs- Closures enable encapsulation and async state
- Misused closures cause memory leaks
function bankAccount(balance) {
return {
deposit(x) { balance += x; },
getBalance() { return balance; }
};
}balancecannot be accessed directly- True privacy without classes



