JavaScript Closures simply explained

Adam Blazek - Jul 10 '23 - - Dev Community

Closures are one of the main pillars of JavaScript. It is also a popular interview question. So, it's really useful to understand what closures are and what we can do with them.

Understanding Closures

Simply said, closures allow accessing variables outside the function. Closure is a combination of a function bundled together with references to its surrounding state. It means that the function can access and manipulate variables located in the outer function's scope even after the outer function has finished. Then if the function is called at a time when the external scope with the variable already doesn't exist, the function still keeps access to the variable. It creates a bubble around our function, it keeps all variables in a scope when the function was created. Let's take a look at the following code snippet. It is a classic example.

const outerFunction = (outerVariable) => {
  const innerFunction = (innerVariable) => {
    console.log('outerVariable:', outerVariable);
    console.log('innerVariable:', innerVariable);
  }
  return innerFunction;
}

const newFunction = outerFunction('outside');
newFunction('inside');
// logs outerVariable: outside
// logs innerVariable: inside

Enter fullscreen mode Exit fullscreen mode

innerFunction is a closure that is created when we call outerFunction and it keeps access to the outerFunction scope after the outerFunction was executed.
Closure is like a bubble
Closure is like a protective bubble. Closure for the innerFunction keeps the variables in the function's scope alive as long as the function exists.

Practical examples

Closures are used a lot in JavaScript, even when we don't always notice it. For example in data privacy, factory functions, stateful functions, and can often be found in JavaScript libraries and frameworks.
Now, we will go through two practical examples where we will explain how the Closures work.

Private variables

JavaScript doesn't support private variables but we can achieve a similar behavior by using a Closure. When we don't want to allow direct access to a variable, we can create accessors - the getters and setters - to enable them outside of the function. Let's describe it better in the following example:

const post = () => {
  let likes = 0; // private variable

  return {
    getLikes: () => {
      return likes;
    },
    like: () => {
      return likes++;
    };
  }
}

var post1 = post();
post1.like();

console.log(post1.likes); // undefined
console.log(post1.getLikes()); // 1

var post2 = post();

console.log(post2.getLikes()); // 0
Enter fullscreen mode Exit fullscreen mode

First, we declare the private variable likes inside the function's constructor. likes is in the function's scope and isn't accessible from outside. Then we create the accessor method for likes count. Defining the getter we give read-only access to the value. The second method is for incremental increasing the number of likes. No one can change the value more than we allow. In the end, if you check the console, you'll see that we can't get likes directly. But, we can find out the value by using getLikes(), or add more likes by calling like().

Callback as a Closure

It is another practical example of when we do asynchronous fetching of data and then updating the UI or logging. Check out this example.

const fetchData = (url, callback) => {
  // It is simulating an asynchronous operation
  setTimeout(() => {
    const data = { name: 'Adam', age: 20 };
    callback(data);
  }, 2000);
}

const processUserData = (user) => {
  console.log('Name:', user.name);
  console.log('Age:', user.age);
}

fetchData('https://example.com/api/user', processUserData);
Enter fullscreen mode Exit fullscreen mode

fetchData function takes two parameters. The first one is an url from which we fetch data. The second one is the callback that will be called after the asynchronous call is done. processUserData function is our callback function and also it is the Closure! When the fetchData is executed then setTimeout is called and the callback has access to the outer user data. And the access remains even after the execution of the fetchData is finished.
This is a common way how callback can access data returned by async fetch and then do various things, for example, update the UI.

Disadvantage

As you can see, the biggest downside is that Closure comes with an extra bag of other variables. This can use up more memory than needed if we're not careful.

Summary

We talked about a Closure as a function having access to variables that were in the outer scope at the time when the closure was defined. Closures have many practical applications. We describe how are used as accessors to private variables - the getters and setters - or together with a callback function for updating the user interface based on asynchronously fetched data.

. . . . .