JavaScript Closures — The Concept Every Interviewer Asks About
If there is one JavaScript concept that shows up in almost every interview, it is closures.
Most beginners find it confusing at first — but once you understand it, you will realize you have been using closures all along without knowing it.
Let's break it down step by step.
Table of Contents
Lexical Environment
Scope Chain
What is a Closure?
Regular Function vs Closure
Can Closures Modify Outer Variables?
Nested Closures
IIFE and Closures
double()() Syntax — What Does It Mean?
Output-Based Interview Questions
Practice Questions
1. Lexical Environment
Before understanding closures, you need to understand lexical environment.
Every time a function runs, JavaScript creates a lexical environment for it. This contains:
Local memory — variables defined inside that function
Reference to the parent's lexical environment — where the function was written in the code
"Lexical" simply means — where the function is written, not where it is called.
function outer() {
let x = 10; // outer's local memory
function inner() {
console.log(x); // inner can access outer's memory
}
inner();
}
outer(); // 10
inner() was written inside outer() — so its lexical parent is outer(). That is why it can access x.
2. Scope Chain
When JavaScript looks for a variable, it follows the scope chain:
First checks its own local scope
If not found, goes up to the parent's scope
Then the parent's parent — all the way up to the global scope
If still not found —
ReferenceError
inner() → middle() → outer() → global scope
This chain of scopes is called the scope chain.
3. What is a Closure?
A closure is a function that remembers the variables from its outer scope — even after the outer function has finished executing.
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
};
}
const counter = outer(); // outer() is done here
counter(); // 1
counter(); // 2
counter(); // 3
// inner still remembers 'count' — that's closure!
What is happening here?
outer()runs and returnsinner()outer()is now finished — normally its variables would be goneBut
inner()still holds a reference tocountSo every time we call
counter(), it remembers and updatescount
This is closure — the inner function carries its outer scope with it.
4. Regular Function vs Closure
| Regular Function | Closure | |
|---|---|---|
| Accesses | Own variables + global variables | Own variables + outer function's variables + global variables |
| After outer function ends | Cannot access outer variables | Still remembers outer variables |
| State | No memory between calls | Maintains state between calls |
// Regular function — no memory
function add(a, b) {
return a + b;
}
// Closure — remembers outer variable
function makeAdder(a) {
return function(b) {
return a + b; // remembers 'a' from outer scope
};
}
const add5 = makeAdder(5);
console.log(add5(3)); // 8
console.log(add5(10)); // 15
5. Can Closures Modify Outer Variables?
Yes. Closures do not just read outer variables — they can also modify them.
Also, closures store a reference to the variable, not a copy of its value. So if the variable changes, the closure sees the updated value.
function outer() {
let count = 0;
return {
increment: function() { count++; },
getCount: function() { console.log(count); }
};
}
const obj = outer();
obj.increment();
obj.increment();
obj.getCount(); // 2
Both increment and getCount share the same closure over count. When increment changes count, getCount sees the new value.
6. Nested Closures
Closures work at multiple levels. An inner function can access variables from all its parent scopes.
function outer() {
let x = 1;
function middle() {
let y = 2;
function inner() {
let z = 3;
console.log(x + y + z); // accesses all three
}
inner();
}
middle();
}
outer(); // 6
How JavaScript finds the variables:
inner() → has z = 3
↑ looks up
middle() → has y = 2
↑ looks up
outer() → has x = 1
↑ looks up
global scope
7. IIFE and Closures
An IIFE (Immediately Invoked Function Expression) is closely related to closures because it creates its own private scope.
const result = (function() {
let secret = 42;
return secret * 2;
})();
console.log(result); // 84
console.log(secret); // ReferenceError — not accessible outside
secret is trapped inside the IIFE — nothing outside can touch it. This is closure being used to create data privacy.
8. double()() Syntax — What Does It Mean?
You might see this pattern in interviews — outer()(). It looks confusing but it is simple.
function outer() {
return function inner() {
console.log("Hello!");
};
}
outer()(); // Hello!
Breaking it down:
First
()→ callsouter()which returns theinnerfunctionSecond
()→ immediately calls that returned function
It is the same as writing:
const fn = outer(); // first ()
fn(); // second ()
outer()() is just a shortcut — two function calls in one line.
Summary Table
| Concept | One Line Explanation |
|---|---|
| Lexical Environment | Local memory + reference to parent scope |
| Scope Chain | The path JavaScript follows to find a variable |
| Closure | Inner function remembers outer variables even after outer function ends |
| Reference vs Value | Closures store reference — they see the latest value, not a snapshot |
| Data Privacy | Use closures/IIFE to hide variables from outside |
Output-Based Interview Questions
Try to guess the output before revealing the answer.
Question 1 — Basic Closure
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Output:
1
2
3
The inner function forms a closure over count. Every call to counter() remembers the previous value and increments it. count is never reset because the closure holds a reference to the same variable.
Question 2 — Closure Stores Reference, Not Value
function outer() {
let x = 10;
function inner() {
console.log(x);
}
x = 20;
inner();
}
outer();
Output:
20
Closure stores a reference to the variable x, not its value at the time the function was created. When inner() runs, it looks up the current value of x, which is 20 at that point — not 10.
Question 3 — Famous var + setTimeout Interview Question
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
Output:
3
3
3
var is function scoped, not block scoped. All three setTimeout callbacks share the same reference to i. By the time 1 second passes, the loop has already finished and i is 3. So all three print 3.
Fix using let:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// Output: 0, 1, 2 — because let creates a new scope for each iteration
Question 4 — Two Separate Closures
function greet() {
let name = "John";
return function() {
console.log("Hello " + name);
};
}
let sayHello = greet();
let sayHello2 = greet();
sayHello();
sayHello2();
Output:
Hello John
Hello John
Each call to greet() creates a new separate closure with its own name variable. sayHello and sayHello2 are independent — they do not share the same closure.
Question 5 — Nested Closure Output
function outer() {
let x = 1;
function middle() {
let y = 2;
function inner() {
let z = 3;
console.log(x + y + z);
}
inner();
}
middle();
}
outer();
Output:
6
inner() accesses z (its own), y (from middle), and x (from outer) through the scope chain. 1 + 2 + 3 = 6.
Question 6 — Closure with Object
function outer() {
let count = 0;
return {
increment: function() { count++; },
getCount: function() { console.log(count); }
};
}
const obj = outer();
obj.increment();
obj.increment();
obj.getCount();
Output:
2
Both increment and getCount share the same closure over count. Closure can not only read but also modify outer variables.
Question 7 — IIFE with Closure
const result = (function() {
let secret = 42;
return secret * 2;
})();
console.log(result);
console.log(secret);
Output:
84
ReferenceError: secret is not defined
The IIFE runs immediately and returns 84. But secret is trapped inside the IIFE's private scope — it cannot be accessed from outside.
Question 8 — Multiplier Closure
function makeMultiplier(x) {
return function(y) {
return x * y;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5));
console.log(triple(5));
console.log(double(10));
Output:
10
15
20
double closes over x = 2 and triple closes over x = 3. They are completely independent closures. Each remembers their own value of x.
Practice Questions — Try These Yourself
1. What will be the output?
function outer() {
var x = 10;
return function() {
console.log(x);
var x = 20;
};
}
outer()();
2. Fix this code so it prints 0, 1, 2 instead of 3, 3, 3:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
3. Create a function makeGreeter that takes a greeting and returns a function that takes a name and logs "[greeting], [name]!".
const sayHi = makeGreeter("Hi");
sayHi("Abhijeet"); // Hi, Abhijeet!
sayHi("Priya"); // Hi, Priya!
4. What is the output?
let fns = [];
for (var i = 0; i < 3; i++) {
fns.push(function() {
return i;
});
}
console.log(fns[0]());
console.log(fns[1]());
console.log(fns[2]());
5. Create a counter using closure that has three methods — increment(), decrement(), and reset().
Closures are used everywhere in JavaScript — in event listeners, API calls, React hooks, and more. Once you understand this concept, a lot of JavaScript will start making sense.
#wemakedevs #javascript #webdev #100DaysOfCode