Closures in JavaScript: Explained Simply and Under the Hood

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

How Closures Work in JavaScript: Analysis Using a Stack Overflow Example

\n\n

If you've ever Googled "JavaScript closures" or read discussions on Stack Overflow, you've probably seen the famous question: "How do JavaScript closures work?". This thread has gathered thousands of views and hundreds of answers — from simple explanations to deep dives into the V8 engine. Why? Because closures are one of those concepts that first seem like magic, and then become your superpower.

\n\n

In this article, we won't just rehash the theory. We'll look under the hood of JavaScript, break down what happens in memory, and finally understand why this code works exactly the way it does. Ready? Let's go.

\n\n

What is a Closure? A Definition Without Fluff

\n\n

Let's start with the core. A closure is a function that "remembers" variables from its outer scope, even after that outer function has finished executing.

\n\n

Sounds like an incantation? Let's jump straight to the code. Here's a classic example you'll find on Stack Overflow:

\n\n
function createCounter() {\n    let count = 0;\n    return function() {\n        count++;\n        return count;\n    };\n}\n\nconst counter = createCounter();\nconsole.log(counter()); // 1\nconsole.log(counter()); // 2\nconsole.log(counter()); // 3
\n\n

Here, createCounter returns an anonymous function. This inner function "closes over" the variable count. Even after createCounter has finished executing (we think all variables inside it should disappear), count continues to live and change with each call to counter(). That's a closure.

\n\n

Under the Hood: Lexical Environment

\n\n

To understand how this works, we need to look inside the JavaScript engine. Every function, when created, has a hidden property [[Environment]]. It stores a reference to the Lexical Environment in which the function was created.

\n\n

A Lexical Environment is simply an object that contains:

\n
    \n
  • An Environment Record — all variables and functions declared in that scope.
  • \n
  • A reference to the outer environment (outer) — the parent scope.
  • \n
\n\n

When we call a function, a new Lexical Environment is created for that call. The inner function gains access to the outer function's variables through the scope chain.

\n\n

Let's go back to our example. When we declare createCounter, a global Lexical Environment is created. Inside it are the variable createCounter (the function) and counter (currently undefined).

\n\n

When we call createCounter():

\n
    \n
  1. A new Lexical Environment is created for this call. Inside it is the variable count = 0.
  2. \n
  3. An anonymous function is created. Its [[Environment]] points to the current environment (where count lives).
  4. \n
  5. The function is returned and assigned to counter.
  6. \n
\n\n

And here's the most interesting part: createCounter has finished, but its Lexical Environment is not removed by the garbage collector! Because there is a reference to it — the inner function's [[Environment]]. The environment "hangs" in memory as long as the counter function itself exists.

\n\n

Now, every time we call counter(), a new environment is created for that call, which, through the chain, sees that same old environment with count. That's how we get the "remembering" effect.

\n\n

Why Is This Important? Real-World Scenarios

\n\n

Closures are not just an academic concept. They are used everywhere. Let's look at a few practical examples.

\n\n

1. Private Variables (Encapsulation)

\n\n

JavaScript doesn't have true

Blogs

Book Recommendations