Understanding closures in JavaScript

arturogbruno

Arturo G. Bruno

Posted on March 1, 2022

Understanding closures in JavaScript

As a developer, one of the great things about working in this field is that you are learning new things everyday. Some days it may just be something small and others it could be something bigger proving to have a significant impact on your work. Either way, I think that the continuous learning process is one of the most appealing aspects of this job. The other side of the coin is, of course, that frequently you find yourself struggling with something, but that’s part of the challenge and the beauty of it, isn’t it?

Up until now, through my still short journey in the development world, I have struggled to a different degree with several topics. One of those is closures, which seems to be a concept that a lot of people find difficult to wrap their heads around. So let’s take a closer look!

What is a closure?

Before starting to define what a closure is, I want to point out a couple of things:

  • Closure is a concept that is common to most programming languages, but in this article (as its title suggests) the focus will be closures in JavaScript.

  • If you also work with JavaScript, chances are that you have used closures even without totally realising. This is because, although it can be a bit difficult to grasp, this concept is core to the language. Despite being so fundamental, it is often perceived as something “hidden”, so a lot of people don’t pay much attention to it. However, understanding such an important aspect of the language is key and that way it will be much easier to take advantage of it while coding.

The academic definition, so to say, is: a closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

In other words, closure is when a function remembers and can access variables from outside its scope, even when that function is executed in a different scope.

Let’s see the first example. By the way, I will be using arrow functions in some code snippets, as the closure mechanism works both for traditional function definition and arrow functions.

const greet = msg => name => console.log(`${msg}, ${name}`);

const message = greet('Hello');

message('reader'); // Hello, reader
Enter fullscreen mode Exit fullscreen mode

In the example above, there’s a function greet that receives a parameter msg and returns a reference to an instance of another function that takes name as parameter and prints a string in the console. Then the outer function greet is called with the argument ‘Hello’, creating an instance of the inner function. This inner function closes over (remembers) the variable msg, which is the parameter of the outer function and belongs to an outer scope. The inner function is returned and its reference is assigned to message. Finally, message is called with the ‘reader’ argument and that results in the text ‘Hello, reader’ being printed in the console.

The inner function can remember and access a variable from an outer scope, msg in this case. That is happening thanks to the closure mechanism. Without it, we would expect that, once the function greet finishes its execution, its variables will be removed from memory by the garbage collector. But here, since the inner function instance is assigned to message, the closure is preserving the msg variable.

One important thing to notice is that closures don’t just remember the variable’s values. They preserve a direct link to the variables, so they are aware of changes on those variables over time.

In the definition of closure there’s another important concept mentioned, which is tightly bound to closures: lexical scope. We are going to review a little bit about the scope and lexical scope concepts, as they will be helpful in fully understanding closures.

The lexical scope (and how it relates to closures)

Scope refers to the current context of the program’s execution and it’s also what determines the visibility and accessibility of variables. Limiting the accessibility of variables is important: among other things, it keeps variables isolated, so different scopes can have variables with the same name without collision. In the end, scope helps us to keep the code tidy, well structured, understandable and maintainable.

In JavaScript we can differentiate between global scope and block scope:

  • Global scope: it is the outermost part of a file and comprises the code that is not inside any function or block. I don’t want to dive deep into the details, but keep in mind that different JavaScript environments handle global scope in a different way.

  • Block scope: a code block (such the ones created by an if statement or a function) creates a new scope. A special case here are variables declared with the var keyword, as for them a new scope will be created only when placed inside a function block.

A crucial property of scopes is nesting. Scopes can be nested to any arbitrary depth and for any given expression, only variables at the same nesting level or in outer scopes are accessible. On the contrary, variables from inner scopes are not reachable. This behaviour is called lexical scope.

Let’s take a look at an example:

const mainMsg = 'Welcome back'; // mainMsg belongs to the global scope

const greet = name => {                   // greet function creates a new function scope
  const noName = 'guest';                 // noName variable has function scope
  if (name) {                             // if block creates a new block scope
    const endOfMsg = 'Have a nice day!';  // endOfMsg variable has block scope
    console.log(
      `${mainMsg} ${name}. ${endOfMsg}`
    );
  } else {
    console.log(
      `${mainMsg} ${noName}`
    );
  }
};

greet('Jenny'); // Welcome back Jenny. Have a nice day!
Enter fullscreen mode Exit fullscreen mode

As previously stated, the most important rule regarding nested scopes is that inner scopes can access variables in the outer scope, but not the other way around. In the previous code snippet, we can observe that rule: mainMsg is being used inside the if/else block, which, in turn, is inside the greet function. Also the variable noName is accessible in the if/else block, although defined outside of it. However, we cannot use the variable noName, for example, out of the function where it’s defined.

Now that we have recapped the main aspects of scope and lexical scope, let’s go back to our main focus, closures.

Closures, more closely

So… what does lexical scope have to do with closures? Well, basically, we can say that closures are a result of lexical scope in those languages that have functions as first class citizens (functions are treated as any other variable), as is the case with JavaScript.

When a function makes reference to variables from an outer scope, and that function is passed around as a value and then executed in a different scope, it maintains access to the variables of its original scope. Take a moment now to review the first example in this article and observe how that is exactly what is happening. That is a closure.

There are some key aspects we can highlight at this point:

  • A function has to be implicated.

  • At least one reference to a variable from an outer scope should exist.

  • The function should be executed in a different scope than where it was defined.

Let’s extend a little bit on these three aspects:

We can only talk about closures when talking about functions, as they are a behaviour that only functions have. Therefore, closures don’t apply for objects or classes (although a class’ method may have closures).

Now, the function should have one or more references to variables from an outer scope. Of course this is needed for the closure to exist, as those are the variables that will be eventually remembered and accessed when the function is executed.

Another requirement is that the function has to be executed in a different scope from where it was originally defined. Otherwise, closures wouldn’t be needed, as outer variables would be accessible anyway due to the rules of lexical scope.

I hope the concept is clearer now, but let’s check another easy example and dig a bit more on the most important characteristics of closures:

function addTo(num1) {
  return function sum(num2) {
    console.log(num1 + num2);
  }
};

const add3 = addTo(3);
const add10 = addTo(10);

add3(7); // 10
add10(35); // 45
Enter fullscreen mode Exit fullscreen mode

Here we can see closures in action again. Now we are going to pay attention to this: the addTo function gets called twice, with 3 and 10 as arguments respectively. Every time we execute addTo, a new sum function is created and returned and each one of those instances has its own closure or, in other words, each closure has its own lexical scope. This implies that each instance of sum closes over its own num1 variable. As we already know, those num1 variables aren’t deleted, despite the function addTo had finished its execution, as they are participating in the closure mechanism. Then, when we execute add3(7) and add10(35), each of these functions remembers and is able to access its own num1 variable and, therefore, carries out the operation, logging 10 y 45 to the console respectively.

What all that means, in short, is that a closure is created for each instance of the function instead of having one for the function’s single definition.

Another important aspect of closures, glossed over in the beginning of this article, is that they don’t just remember the value of the variables they hold. Actually, they have complete access to read and write on those variables. So, what is remembered by the closure is not a snapshot of the value at a given point in time; the aforementioned variables are kept alive (instead of being deleted by the garbage collector), so they are fully available.

Let’s see an example of this in the following code snippet of a simple counter:

function createCounter() {
  let count = 0;

  return function getCount() {
    count += 1;
    console.log(count);
  };
};

const myCounter = createCounter();

myCounter(); // 1
myCounter(); // 2
myCounter(); // 3
myCounter(); // 4
Enter fullscreen mode Exit fullscreen mode

As we already know, the inner function getCount closes over the variable count, which is kept alive as part of the closure. The difference with the previous examples is that now we are updating the value of that variable inside of the inner function (count += 1). Then we call the function createCounter that returns an instance of the inner function, which is saved to myCounter. Finally, we execute myCounter four times and each time we get an incrementing number from 1 to 4 in the console. This demonstrates that the variable count can be read and also updated.

Common use cases

Closures are a fundamental pillar of the language and its use lets us associate a function with some data from the lexical scope. Different scenarios where closures are commonly used are:

  • Event handlers

Closures are often used in event handlers. In the following example, we add an event listener to a button so when it is clicked, a message is displayed.

const loginBtn = document.getElementById('loginBtn');
const welcomeText = document.getElementById('welcomeText');

const showGreeting = message => {
  loginBtn.addEventListener('click', function handleClick() {
    welcomeText.innerText = message;
  });
};

showGreeting('Welcome back!');
Enter fullscreen mode Exit fullscreen mode

In this case, the function handleClick captures the variables message and welcomeText from the lexical scope. When the button is clicked, both of these variables still exist and are accessible. Of course, this is happening because of the closure.

  • Callbacks
const searchUserById = id => {
  fetch(`https://myapi.com/users/${id}`)
  .then(response => response.json())
  .then(data => console.log(`The user with id ${id} is called ${data.name}`));
};

searchUserById(27); // The user with id 27 is called Michael
Enter fullscreen mode Exit fullscreen mode

In the previous example, the closure is happening in the callback function that is logging the message with the name of the user. That callback function is executed at a later point in time when the fetching process completes and returns the corresponding data, and by then the function searchUserById has already ended its execution. However, the callback function still has access to the id variable, as it was captured by the closure.

  • Currying

One more scenario where closures are common is on curried functions. Currying is a technique of working with functions by which a function that takes several arguments is broken down into several functions and each one takes only one argument. So a curried function is instantly evaluated and returns another function. But let’s take a look at an example to see how this works and what it has to do with closures:

const add = num1 => num2 => console.log(num1 + num2);

add(5)(2); // 7

const add5To = add(5);
add5To(2); // 7
Enter fullscreen mode Exit fullscreen mode

The add function can be called in two different ways, as can be seen in the code snippet. That is a curried function. But what is interesting to us is that this kind of functions relies on closures. The inner function captures the variable num1 from the lexical scope, so it can be used when this function gets executed.

Benefits of using closures

We can summarise the main benefits of using closures as follows:

  • Closures are useful because they let you associate data from the lexical environment with a function that operates on that data.

  • Closures can improve our program’s efficiency. Used when appropriate, closures help efficiency as the function can remember previous bits of information instead of having to compute them each time.

  • Closures can improve code readability and cleanliness. With closures we bundled together the function and some references to its lexical environment, so the result is a function instance which is cleaner because the remembered information doesn’t need to be provided in every call.

Conclusion

Throughout this article we have defined what a closure is, as well as other related concepts, and have gone over different examples to explain how this important mechanism works in JavaScript. Also, at some points, we have commented on some of its nuances to have a bigger picture of the topic.

Hopefully this article has shed light into this subject and can help you to achieve a better understanding of what closures are, as well as being able to recognise where they are used in programs and take advantage of their benefits in your own code.

Thanks for reading!

💖 💪 🙅 🚩
arturogbruno
Arturo G. Bruno

Posted on March 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related