Understanding JavaScript Closures
Unlock the mystery and logic behind JS Closures

Definition of Closure [ Function + Lexical scope]
Closure is a function bundled together with references of its surrounding (Lexical) environment.
A closure forms when a function retains access to variables from its outer scope, even after the outer function has completed execution.
Closures depend on three concepts:
Lexical scope — variable lookup is determined by the location of code in source files.
Execution context — the environment created when a function runs (variables, arguments, scope chain).
Memory persistence — values from an outer scope can remain in memory as long as they are referenced.
Examples of Closures
Basic Example
function outer () {
let count = 0;
function inner () {
count++;
console.log(count);
}
return inner;
}
const counter = outer ();
counter (); //1
counter (); //2
counter (); //3
console.log(counter); //function inner(){.....}
What happens:
When
outer()runs, a new execution context is created withcountand theinnerfunction.outerreturns theinnerfunction, which closes over thecountvariable and socounterholds that inner function.Even after
outerfinishes,countremains accessible toinner, so each call tocounterincrements the samecount.
Mental model:
Variable points to function —
counterpoints toinner.Function points to environment —
innerpoints to{ count: 0 }.
Because inner references count, the memory for that variable is preserved.
Closure with an anonymous (unknown) function
function user () {
let name = "Atharv";
let age = "21";
return function () {
console.log(name, age); //Atharv 21
};
}
const showUser = user ();
showUser ();
In such examples, a function is immediately returned and variables from surrounding scopes can be accessed.
return function () {
console.log(name, age);
};
//This block of code uses anonymous function, which is equivalent to the below code.
const innerFunction = function () {
console.log(name, age);
};
return innerFunction;
Closure formation when a function is stored to a variable
Functions in Js are not special, they are simply the values which can be assigned to variables.
Let's imagine this example, showing closure persisting
function a () {
var x = 10;
var b = function y () {
console.log(x);
}
return b;
}
const counter = a (); // a() has returned, but `counter` still references the inner function
counter(); //10 -`x` is kept alive by the closure
What happens (step‑by‑step):
When
a()is called, a new execution context is created fora. Inside that context the variablexis set to10.bis assigned a function expressionfunction y() { console.log(x); }. That inner function is created with a lexical link (closure) toa's environment, so it can accessx.b()is invoked while still insidea, soconsole.log(x)prints10.After
areturns, if there are no remaining references to the inner function, the environment can be freed.
Functions can be passed as an argument for another function
Remember you used to pass value as an argument, in a similar way a function can be passed as an argument. This is central to callbacks, higher order functions, and functional patterns.
Have a look at example below;
- Passing a named function:
function greet(name) {
console.log('Hello ' + name);
}
function callWithName(callback) {
const name = "Atharv";
callback(name); //calling the function passed as argument
}
callWithName(greet); // Hello Atharv
What happens:
greetis definedcallWithNametakes a parametercallbackPasses
greetas argument tocallWithNameInside
callWithName,callback( )callsgreet
Such functions are called callback functions. Think of it as "I am not giving results; I am giving instructions to run later.
Common uses
Array methods: map, filter, reduce
Async callbacks: setTimeout, event listener etc
Closure with private state
Closures are often used to create private state — variables that are accessible only to specific functions and not visible from outside. This gives us encapsulation without classes or object internals.
function createCounter() {
let count = 0; // private
return function () { // closure captures `count`
count += 1;
return count;
};
}
const c1 = createCounter();
c1(); // 1
c1(); // 2
const c2 = createCounter();
c2(); // 1 (different private `count`)
Each function call creates new closure and different private state.
From above code, it is clear that when c1() and c2() are invoked they create two different closures, each holding a distinct private `count`.
Function Factories
A function that creates as well as returns new functions. Each returned function typically captures values from the factory's lexical environment.
This can be illustrated as follows:
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
console.log(add5(3)); // 8
What happens:
makeAdder()is defined with aparameter xand returns ananonymous functionwithparameter y- it is similar to howouter()returnsinnner(). Hereouter = makeAdderandinner = anonymousconst add5 = makeAdder(5); — On invoking
makeAdder(), execution context havex=5. NowmakeAdder()is outer function it must return inner one, so it assigns the anonymous function toadd5.console.log(add5(3)) — According to closure, inner function references to the outer variable. Here
add5already have closure withx=5and when we calladd5(3), this creates execution context withy=3.The function now runs
return x + y, which is equivalent to5 + 3producing output of 8
Pitfalls to focus:
While writing closures ensure to use
letorconst, instead ofvar. why is that so? because let or const areblock scopedand create a fresh binding for each block of code/iteration.Varshowshoistedbehavior and so closures created insideloopsall share thesamevariable.
- The classic loop + closure problem with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 10);
}
// prints: 3 3 3
// All callbacks share the same function-scoped `i`, which ends up as 3 after the loop.
- Using let fixes it (block-scoped, new binding per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 10);
}
// prints: 0 1 2
// Each iteration gets its own `i` binding, so each closure closes over that iteration's value.
Closures capture variables, not values
Key point: A closure captures the variable binding (the lexical environment), not a snapshot of its value.
function x() {
var a = 10;
function y() {
console.log(a);
}
a = 100;
return y;
}
var z = x();
z(); //100
In the above closure example:
Expected output = 10
Actual output = 100 — Because closure captures the variable binding
a, not a snapshot of its value. Sinceais reassigned to 100 before the inner function is invoked, calling the returned function logs 100."
In such cases, function call reads the current value at call-time (execution time), not a definition.
In case you replace
var with constit will be an error pointing "reassignment to the const".
Conclusion
Closures in JavaScript are combination of function and lexical scoping, allowing functions to access variables from outer scopes.
Think of a closure as a function + the box of variables it needs.
Use small examples and experiment in the console — write an outer function, return an inner one, and call it later.
Don’t worry if it feels weird at first; closures are one of the most useful and common patterns in JavaScript.


