Closure in JavaScript

Hello friends, welcome to shrash studio learning, in this article we are going to deep dive into one of the most beautiful, most powerful, and most frequently asked JavaScript interview topics — Closures. We will start from the simplest possible example, gradually build up to advanced real-world patterns, and explore exactly why closures are considered the heart of modern JavaScript. You will learn what a closure truly is, why the language needs it, how the engine internally links a function with its lexical environment, how data privacy, function currying, and the module design pattern all rely on closures, and what common mistakes developers make with them. By the end of this article, you will be able to confidently explain closures in any technical interview and use them with full understanding in your real projects.

What is a Closure in JavaScript

A closure in JavaScript is a function that remembers and continues to have access to the variables from the place where it was originally created, even after that outer function has already finished running. In simple words, a closure is a function bundled together with its lexical environment — that is, the set of variables that were in scope at the moment the function was defined.

This means that even if the outer function returns and its execution context is destroyed, the inner function that was created inside it does not lose connection with those variables. It still carries a permanent reference to them. This link between a function and its surrounding variables is not a copy — it is a live reference, which is why any changes to those variables are still visible through the closure.

Closures are not a special syntax or a keyword. They are simply a natural outcome of how JavaScript handles functions and scopes. Every single function in JavaScript automatically forms a closure with the environment it was created in. You are already using closures whenever you write nested functions — you just may not have realised it yet. Understanding this concept clearly will change how you read and write JavaScript forever.

Why Closures Exist in JavaScript

Closures exist because JavaScript functions are first-class citizens. You can pass a function as an argument, return it from another function, assign it to a variable, and store it inside objects or arrays. To make this work safely, the language must guarantee that when a function is moved around, it still has access to the variables it was originally designed to work with. That guarantee is what closures provide.

If closures did not exist, then passing a function from one place to another would break it — the function would lose its variables the moment its parent finished executing. By giving every function a permanent link to its lexical environment, JavaScript allows functions to travel anywhere in the program while still behaving correctly. This is what enables callbacks, event handlers, asynchronous code, and functional programming patterns to work so elegantly.

Closures also power some of the most important features of modern JavaScript, including data privacy, function currying, memoization, the module design pattern, and the once pattern used to ensure a function runs only one time. Without closures, none of these techniques would be possible. This is why closures are often called the backbone of real-world JavaScript.

Internal Working: How Closures Work Under the Hood

To understand how closures actually work, you need to understand the concept of the Lexical Environment. Every time a function is created in JavaScript, it is attached to the lexical environment of its surrounding code. The lexical environment is essentially a reference to the memory space of the parent scope. This attachment stays with the function forever, even after the parent finishes executing.

When a function is invoked, the JavaScript engine creates a new Execution Context for it. This Execution Context has its own local memory, but it also holds a hidden reference to the lexical environment of its parent. When the function tries to access a variable, the engine first looks in the local memory. If it does not find the variable there, it follows the lexical environment reference to the parent scope, and keeps walking up this chain of parents until it either finds the variable or reaches the global scope. This chain of connected environments is called the Scope Chain.

The magic of closures happens when a function is returned from another function. Normally, when a function finishes, its Execution Context is destroyed and its local variables would be cleaned up by garbage collection. However, if an inner function is returned and still alive somewhere in the program, that inner function still holds a reference to the parent's lexical environment. Because of that reference, the parent's variables are kept in memory and are never garbage-collected. The returned function can continue to read and even modify those variables for as long as it exists.

CLOSURE STRUCTURE

Inner Function

Returned to the outer world
function body (code)
+ hidden reference to parent scope

Lexical Environment

Kept alive by the closure
a : 7
other outer variables...

Practical Examples of Closures

Let us now walk through a series of clear, progressive examples so you can build a rock-solid intuition for closures. We will start with the simplest case and gradually move into the real-world patterns.

Example 1: The Most Basic Closure


function x() {
    var a = 7;

    function y() {
        console.log(a);
    }

    y();
}

x();

// Output: 7

Here, the inner function y prints the variable a, which is defined in the outer function x. Even though a is not declared inside y itself, the inner function can reach outside into the lexical environment of its parent and find a there. This is the simplest possible closure in action — an inner function accessing a variable from its outer function.

Example 2: Returning the Inner Function (The Real Closure)


function x() {
    var a = 7;

    function y() {
        console.log(a);
    }

    return y;
}

var z = x();
console.log(z);   // Output: Æ’ y() { console.log(a); }

z();              // Output: 7

This is the heart of closures. We are calling x() which returns the inner function y. By the time we call z() a few lines later, the outer function x has already completed and its Execution Context has been destroyed. Yet, when we invoke z(), it still prints 7. How? Because the returned function y did not lose its link to the lexical environment of x. It carries that environment along with itself — that bundle is the closure.

Example 3: Closure Remembers the Reference, Not the Value


function x() {
    var a = 7;

    function y() {
        console.log(a);
    }

    a = 100;   // value updated AFTER y is defined
    return y;
}

var z = x();
z();          // Output: 100

This example demonstrates a subtle but critical truth about closures — they hold a reference to the variable, not a snapshot of its value. Even though y was defined when a was 7, by the time we return y, the value of a has changed to 100. When we eventually call the closure, it reads the latest value from the shared memory — hence the output is 100, not 7.

Example 4: Nested Closures and the Scope Chain


function outer() {
    var a = 10;

    function middle() {
        var b = 20;

        function inner() {
            console.log(a + b);
        }

        return inner;
    }

    return middle();
}

var fn = outer();
fn();   // Output: 30

Here the innermost function inner can reach both b from middle and a from outer. This is the scope chain at work. The closure created by inner holds references to every lexical environment above it, all the way up to the global scope. This layered access is how JavaScript resolves variables that are not locally declared.

Output of Each Example

Here is the combined output summary for the examples we just studied. Notice how closures allow inner functions to keep working long after their parents have finished.

Example Output Reason
Basic nested call 7 Inner function reads variable from outer scope
Returning inner function Æ’ y() ... then 7 Closure keeps outer scope alive after return
Updated value before return 100 Closure holds a reference, not a snapshot
Nested closures 30 Scope chain allows access to multiple outer scopes

Step-by-Step Execution of a Closure

Let us carefully trace Example 2 so the full mechanism becomes crystal clear. Imagine we are the JavaScript engine and we are walking through every single step.

Closure Execution Trace — Step by Step
STEP 1: Global Execution Context is created
Memory Phase allocates: x : [function], z : undefined
STEP 2: var z = x(); is encountered
New Execution Context created for x()
Inside x: a : 7, y : [function]
STEP 3: return y
Function y is returned WITH its lexical environment
y now forms a closure with { a : 7 }
STEP 4: x() completes and its Execution Context is popped
But the variable a = 7 is NOT destroyed
The closure reference keeps it alive in memory
STEP 5: z now holds the returned function
console.log(z) prints: Æ’ y() { console.log(a); }
STEP 6: z() is invoked
y looks for a in its own scope: not found
y looks in its closure: finds a = 7
Output: 7

Common Mistakes Developers Make with Closures

Closures are elegant but they come with a few traps. Knowing them in advance will save you a lot of debugging time in real projects.

Mistake 1: Thinking Closures Store a Snapshot of the Value

Many beginners assume that when a function "captures" a variable, it captures the current value. This is wrong. A closure captures a reference to the variable. If the variable changes later, the closure sees the new value.


function createLoggers() {
    var value = 1;

    var logNow = function () { console.log(value); };

    value = 99;

    return logNow;
}

var log = createLoggers();
log();   // Output: 99  (not 1)

Mistake 2: Classic var Loop Trap

One of the most famous closure traps involves using var inside a loop with setTimeout. Because var is function-scoped, all callbacks share the same i and end up printing the final value.


for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

// Output after 1 second:
// 4
// 4
// 4

The fix is to use let, which is block-scoped. Each iteration creates a fresh i, and each closure captures its own copy.


for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

// Output after 1 second:
// 1
// 2
// 3

Mistake 3: Creating Memory Leaks by Accident

Because closures keep their entire lexical environment alive, they can accidentally prevent large objects from being garbage collected. If a closure captures a huge array just to read a small value from it, the whole array stays in memory as long as the closure exists.

Tip: If you only need a specific field, extract it into a local variable before creating the closure. This way the closure only holds a reference to the small value, not the whole object.

Mistake 4: Not Realizing Every Function is a Closure

Developers often think closures are a rare or advanced feature. In reality, every single function in JavaScript creates a closure with its surrounding environment. Once you accept this, many language behaviors — like callbacks, event handlers, and async code — become much easier to understand.

Comparison: Closures Across Different Languages

Closures are not unique to JavaScript, but JavaScript implements them in a particularly elegant and accessible way. Here is a quick comparison with other popular languages.

Language Supports Closures? Notes
JavaScript Yes — first-class Every function creates a closure automatically
Python Yes Inner functions capture outer scope; requires nonlocal for writes
Java Limited (lambdas) Variables must be effectively final
C No No support for nested functions or closures
C++ Yes (lambdas) Explicit capture syntax required
Swift / Kotlin / Rust Yes First-class support with explicit capture semantics

Of all these, JavaScript is arguably the most closure-friendly language because you do not need any special syntax, keywords, or annotations to create one. Closures happen automatically and seamlessly every time you nest a function.

Real-World Usage of Closures

Closures are not just theory — they power some of the most important and elegant patterns in real-world JavaScript applications.

Scenario 1: Data Privacy (Private Variables)

Closures are the classic way to create private state in JavaScript. Variables defined inside the outer function cannot be accessed from outside, but the inner functions returned from it can use them freely.


function createCounter() {
    var count = 0;

    return {
        increment: function () { count++; },
        decrement: function () { count--; },
        getValue: function () { return count; }
    };
}

var counter = createCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();

console.log(counter.getValue());   // Output: 2
console.log(counter.count);        // Output: undefined (private!)

The count variable is completely hidden. Outside code cannot read, write, or corrupt it. Only the three methods returned from createCounter can interact with it — this is genuine data privacy built with nothing but closures.

Scenario 2: Function Currying

Currying is a functional programming technique where a function takes arguments one at a time, returning a new function for each additional argument. Closures make currying possible and elegant.


function multiply(a) {
    return function (b) {
        return function (c) {
            return a * b * c;
        };
    };
}

console.log(multiply(2)(3)(4));   // Output: 24

var doubleAndTriple = multiply(2)(3);
console.log(doubleAndTriple(5));  // Output: 30
console.log(doubleAndTriple(10)); // Output: 60

Scenario 3: The Once Pattern

Sometimes you want a function to run only once, no matter how many times it is called. Closures make this extremely clean.


function once(fn) {
    var called = false;

    return function () {
        if (!called) {
            called = true;
            return fn.apply(this, arguments);
        }
        return "Already executed";
    };
}

var initApp = once(function () {
    console.log("App initialised");
});

initApp();   // Output: App initialised
initApp();   // Output: Already executed
initApp();   // Output: Already executed

Scenario 4: Memoization (Caching Results)

Closures are perfect for storing cached results of expensive computations, so repeated calls with the same input return instantly.


function memoize(fn) {
    var cache = {};

    return function (n) {
        if (cache[n] !== undefined) {
            console.log("From cache");
            return cache[n];
        }
        console.log("Computing...");
        var result = fn(n);
        cache[n] = result;
        return result;
    };
}

var slowSquare = memoize(function (x) {
    return x * x;
});

console.log(slowSquare(5));   // Computing... 25
console.log(slowSquare(5));   // From cache 25
console.log(slowSquare(6));   // Computing... 36
console.log(slowSquare(5));   // From cache 25

Scenario 5: The Module Design Pattern

Before ES6 modules, closures were the only clean way to create reusable modules with public APIs and private internals. This pattern is still widely used today.


var UserModule = (function () {
    var users = [];

    function addUser(name) {
        users.push(name);
    }

    function getUsers() {
        return users.slice();
    }

    return {
        addUser: addUser,
        getUsers: getUsers
    };
})();

UserModule.addUser("Rahul");
UserModule.addUser("Priya");

console.log(UserModule.getUsers());   // Output: ["Rahul", "Priya"]
console.log(UserModule.users);        // Output: undefined (private!)

Key Insight: Almost every advanced JavaScript pattern — callbacks, async code, React hooks, Redux reducers, currying, memoization, private state, modules — depends on closures working correctly behind the scenes. Mastering closures is mastering modern JavaScript itself.

Summary

A closure is simply a function bundled together with its lexical environment. This means the function permanently remembers the variables that were in scope at the moment it was created, and it can continue to access them even after the outer function has finished executing and its Execution Context has been destroyed.

Closures exist in JavaScript because functions are first-class citizens. They can be returned, passed around, and stored anywhere in the program. To keep them working correctly wherever they go, the language attaches each function to a live reference of its surrounding scope. This attachment, combined with the scope chain, is what enables closures to function naturally without any special syntax.

We saw closures in action through progressive examples — from the simplest nested function, to returned functions, to closures that hold references rather than snapshots, and finally to deeply nested closures accessing multiple outer scopes. We then applied closures to real-world patterns that every JavaScript developer should know: data privacy with private variables, function currying, the once pattern, memoization for caching, and the classic module design pattern.

We also covered the common mistakes developers make — assuming closures capture values, falling into the var loop trap, creating accidental memory leaks, and not realising that every function is inherently a closure. When you understand closures deeply, these traps become easy to avoid, and you unlock a new level of confidence in your JavaScript code. Closures are truly the soul of JavaScript, and mastering them is one of the most rewarding milestones in your journey as a developer.

Concept Key Takeaway
Closure Function + its lexical environment bundled together
Lexical Environment The scope where the function was originally defined
Scope Chain Linked list of environments used to resolve variables
Reference, Not Snapshot Closures read the current value from memory, not the old one
Data Privacy Hide state in outer function, expose only via returned API
Currying Break a multi-argument function into a chain of single-argument ones
Once / Memoize / Module Classic real-world patterns built entirely on closures
Common Trap Using var inside a loop with setTimeout — fix with let

Chakrapani U

Hi, I’m Chakrapani Upadhyaya, an IT professional with 15+ years of industry experience. Over the years, I have worked on web development, enterprise applications, database systems, and cloud-based solutions. Through this blog, I aim to simplify complex technical concepts and help learners grow from beginners to confident, industry-ready developers.

Previous Post Next Post

نموذج الاتصال