JavaScript Closures: An Ultimate Guide [in 2022]

In JavaScript, closure means an inner function can access variables that belong to the outer function.

More importantly, this is possible even after the execution of the outer function.

javascript closure illustration
The closure is nothing but the fact that a function always has access to its surroundings.

This article is split into two sections:

  • A quick explanation of closures
  • A detailed guide on closures

In the quick explanation, you are going to learn how closures work in a nutshell with examples.

In the detailed guide, you are going to dig deeper into the implementation details of JavaScript to truly understand closures with lexical environments.

If you are here for a short answer, a quick explanation is enough for you. However, to truly understand how closures work, you should read the whole article.

Closures: A Quick Explanation

In JavaScript, a closure means a function has access to the variables in the outer scope.

To demonstrate this, let’s start with regular functions.

function hello() {
  const sentence = "Hello world"
  console.log(sentence);
}

hello(); // prints "Hello world" to the console

When you call the function hello(), the code in between the curly braces runs.

When the code has finished running, you can no longer access the variables inside the function.

This is all just basics you probably already knew.

Now, let’s take a look at a similar situation. This time, let’s create a function inside a function.

function outer() {
   const a = "Hello ";

   function inner() {
     const b = "World"; 
     console.log(a+b);
   }

   return inner;
}

Here the function outer() returns a function inner().

Let’s call the outer() function and assign the result to a constant:

const greet = outer()

Now let’s call the function stored in this constant:

greet()

This logs the following message to the console:

Hello world

But how is this possible?

If you take a look at the code, you see the constant a is defined outside the inner() function.

When the outer() function call finishes, there should be no trace of the constant a.

In other words, when you call greet() you would suppose to see an error because you are trying to log a+b when only b is defined.

javascript function can access variables from outer scope
The constant “a” is specified outside the inner() function.

But as you saw, the greet() was clearly able to access both a and b.

This is because it is a closure.

A closure means an inner function has access to variables in its surrounding block of code even after the surroundings are gone.

In the outer-inner function setup above, this can be seen as the inner() function always has access to a defined in the scope of the outer() function.

Now you understand what is closure in JavaScript.

Before digging into the details, let’s see a practical example.

Practical Example of Closures in JavaScript

The previous example demonstrated well how to use closures in JavaScript.

Now, let’s take a look at a real-life application of closure.

Let’s build a counter function that increments a counter variable.

Here is a naive approach you would probably use if you did not know about closures:

var counter = 0;

function increment() {
  counter++;
}

Calling increment() works fine and this function does its job like it is supposed to.

But there is a problem.

The counter variable can be tweaked outside the increment function as well. Thus someone can set it to whatever value and mess the counter up.

This is not practical.

To build a reliable counter, you need to bundle it up with the increment function. This way no other parts of the code can mess it up.

This is where closures come in handy.

Let’s re-implement the counter by using a closure. This means a similar nested function setup you saw earlier:

function createCounter() {
   let counter = 0;
   function increment() {
     counter++;
     console.log(counter);
   }
   return increment;
}

Now you can use the counter reliably:

let add = createCounter();

add();
add();
add();

Output:

1
2
3

But wait a minute. How does the variable add know the value in counter? The counter is specified in the createCounter() function.

Similar to the example in the previous chapter, the increment() function always has access to the counter constant. Even after the execution of the createCounter() function.

This is because it is a closure.

In JavaScript, an inner function always has access to the variables of the outer function due to the closure nature of the code.

Now you understand what is closure in a nutshell.

However, in this guide, we used words like block, nested function, and scope quite arbitrarily.

In the next chapter, we are going to build an understanding of closures from the ground up.

You are going to learn what is:

  • A block of code.
  • Nested function.
  • Lexical environment.
  • [[Scopes]] property

Last but not least, we are going to put all this together to truly understand what closure is.

Next, let’s take a deeper and more detailed guide to understand what makes closures possible and how do they really work.

Closures: A Detailed Guide

In JavaScript, a closure is a function inside a function that has access to the outer function’s scope all the time.

This access remains even when the outer function has completed running.

In practice, this means a closure function has access to the variables and arguments of the outer function.

To fully understand the concept of closures, you need to understand variable accessibility, blocks, and lexical environments in JavaScript.

Code Blocks in JavaScript

A block of code in JavaScript refers to code written inside of curly braces.

When you declare a variable inside a block of code {…} in JavaScript, you can only access the variable inside that block of code.

For instance, let’s try to access a variable declared inside a block of code from outside:

{
  let msg = "Hello";
  console.log(msg); // Hello
}

console.log(msg); // Error: msg is not defined

This applies to any blocks of code, such as if statements, for and while loops, functions, and so on.

For instance:

if (1 == 1) {
  let msg = "Hello";
  console.log(msg); // Hello
}

console.log(msg); // Error: msg is not defined

This is a great feature because it lets you create block-specific local variables. This makes it possible to write isolated code without having to worry about the variable names or the surroundings.

A block of code is like an isolated space of code.

Next, let’s talk about nested functions.

Nested Functions in JavaScript

A nested function means a function inside a function.

For example:

function outer() {
   const a = "Hello ";

   function inner() {
     const b = "World"; 
     console.log(a+b);
   }
}

If we talk about code blocks, a nested function is a block of code inside a block of code.

Next, let’s see what happens behind the scenes when we create a new block of code.

Lexical Environment in JavaScript

In JavaScript, every piece of code is associated with a special hidden object known as the lexical environment.

Each script has its own lexical environment. This is called the global lexical environment. It is always present in your code.

When you create a new block of code, such as a function, if-statement, or for loop, you are creating a new lexical environment.

In the case of a nested block of code, you create a lexical environment inside a lexical environment.

You cannot directly modify the lexical environment object. It is a hidden object that operates behind the scenes.

Each lexical environment stores two key properties:

  1. Environment record.
  2. Reference to outer lexical environment.

Let’s understand what these two mean.

1. Environment Record

The environment record stores all the local variables and properties that belong to the lexical environment in question.

Each variable/constant you ever write is just a property of the environment record.

For example, a function’s lexical environment record stores everything found inside that function.

When you modify a variable in your JavaScript code, you are actually changing the property in an environment record object.

2. Reference to Outer Lexical Environment

The second key property of a lexical environment is the reference to the (possible) outer lexical environment.

For example, a function creates a lexical environment inside the global lexical environment. The function’s environment then references to the global lexical environment.

Now you have a conceptual understanding of how lexical environments work in JavaScript.

Next, let’s take a visual approach to further understand what is going on.

Variables and Lexical Environments

Each JavaScript script has a global lexical environment.

All the variables (not surrounded by a block of code) belong to this global lexical environment.

Here is an illustration of a piece of code with a single variable and its corresponding global lexical environment:

lexical environment of a piece of code.

Because there is no block of code in this example, the one and only lexical environment is the global lexical environment.

The global lexical environment is not enclosed by any outer lexical environment, thus it points to a Null address.

If you update the variable in the script, you are essentially updating the properties of the lexical environment object.

Functions and Lexical Environment

As stated earlier, any new block of code creates its own lexical environment.

For instance, a function call creates a new inner lexical environment.

The function’s lexical environment stores all the variables in the function. It then references the global environment.

The global lexical environment, in turn, stores the variable greeting, and the function greet().

Here is an illustration:

nested lexical environments
Calling the greet() function creates a temporary lexical environment for the function.

Once the function finishes execution, the lexical environment of the function gets removed from the memory. Thus, you cannot use the function’s variables outside of it.

For example, accessing the name parameter is not possible outside of the greet() function.

Next, let’s take a look at how functions are able to use variables defined outside of the functions.

Searching Values in the Lexical Environment

When you access JavaScript variables they are searched for in the lexical environments.

If a variable with a specific name is not found, the search continues to the outer lexical environment. This process continues all the way up to the global lexical environment.

If the value is not found in the global environment, an error will occur.

Here is a behind-the-scenes illustration of how the lexical environments are searched.

searching lexical environments recursively
Example of how the variable msg is searched.

Let’s take a closer look at the searching process in the above image.

  • The returned function is trying to use the constant msg.
  • The msg constant is not found in the lexical environment of the returned function.
  • Next, the outer function info() is searched through. The msg does not appear there either.
  • Finally, the global lexical scope is searched and there is the declaration of the msg constant.

This is how a JavaScript function can access variables declared outside of it.

Closures and Lexical Environments in JavaScript

All JavaScript functions store a hidden reference to the lexical environment where they were created in.

This info is stored in the [[Scopes]] property of the function.

When you have a function inside a function, the inner function has access to the outer function’s variables even after the outer function has finished execution.

This is possible because the function has access to the lexical environment it was created in.

This feature is called closure.

A closure is a JavaScript function’s ability to access the lexical environment it was created in.

Let’s go back to the example of the counter function from earlier chapters.

The counter is a classic way to demonstrate closures in JavaScript.

This time, let’s inspect the closure in the JS console to better understand how it works.

Feel free to copy-paste the following code in the JavaScript console:

function createCounter() {
   let counter = 0;
   function increment() {
     counter++;
     console.log(counter);
   }
   return increment;
}

let add = createCounter();

// Inspect the add constant
console.dir(add);

This piece of code executes the outer function createCounter(). It returns the inner closure function increment(). The closure is then stored into a variable called add.

The console.dir(add) displays info about the variable add in the console.

javascript console.dir function shows info about a variable
The add variable can still access the lexical scope of the createCounter function.

By looking at the console output, you can see that the add variable refers to the increment() closure inside the createCounter() function. The add variable can still access the createCounter() function’s lexical environment.

The add variable’s [[Scopes]] property stores a reference to the createCounter() function’s lexical environment where the counter variable was initialized to 0.

If you now call add once, you can see the counter property increase by 1:

javascript [[Scopes]] property inspection in a closure

This is how the counter closure works.

The increment() function looks back to the lexical environment it was created in and modifies the environment’s variable counter.

Conclusion

Today you learned how closures work in JavaScript.

To put it short, when you have a function inside another function, a closure is automatically created.

A closure means a function has access to the scope of the outer function. This applies even when the outer scope is destroyed.

JavaScript scoping of a nested function

A closure function can thus use variables from the function that surrounds it even after the surrounding function has finished execution.

Behind the scenes, this all boils down to lexical environments.

Each block of JavaScript code has a lexical environment.

A lexical environment stores variables and properties of the block code.

When you create a nested function, the inner function saves a reference to the lexical environment of the outer function.

The inner function can then use it even after the outer function has finished execution.

Thanks for reading.

Happy coding!

Further Reading

JavaScript Interview Questions

50+ Web Development Buzzwords