Skip to main content

Command Palette

Search for a command to run...

Understanding JavaScript Closures

Unlock the mystery and logic behind JS Closures

Updated
7 min read
Understanding JavaScript Closures
C
Atharv this side! Web development and Python

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:

  1. Lexical scope — variable lookup is determined by the location of code in source files.

  2. Execution context — the environment created when a function runs (variables, arguments, scope chain).

  3. 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 with count and the inner function.

  • outer returns the inner function, which closes over the count variable and so counter holds that inner function.

  • Even after outer finishes, count remains accessible to inner, so each call to counter increments the same count.

Mental model:

  • Variable points to function — counter points to inner.

  • Function points to environment — inner points 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 for a. Inside that context the variable x is set to 10.

  • b is assigned a function expression function y() { console.log(x); }. That inner function is created with a lexical link (closure) to a's environment, so it can access x.

  • b() is invoked while still inside a, so console.log(x) prints 10.

  • After a returns, 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:

  • greet is defined

  • callWithName takes a parameter callback

  • Passes greet as argument to callWithName

  • Inside callWithName, callback( ) calls greet

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 a parameter x and returns an anonymous function with parameter y - it is similar to how outer() returns innner(). Here outer = makeAdder and inner = anonymous

  • const add5 = makeAdder(5); — On invoking makeAdder(), execution context have x=5. Now makeAdder() is outer function it must return inner one, so it assigns the anonymous function to add5.

  • console.log(add5(3)) — According to closure, inner function references to the outer variable. Here add5 already have closure with x=5 and when we call add5(3), this creates execution context with y=3.

  • The function now runs return x + y, which is equivalent to 5 + 3 producing output of 8


Pitfalls to focus:

While writing closures ensure to use let or const, instead of var. why is that so? because let or const are block scoped and create a fresh binding for each block of code/iteration. Var shows hoisted behavior and so closures created inside loops all share the same variable.

  1. 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.
  1. 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. Since a is 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 const it 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.

More from this blog