DEV Community

Cover image for What is a closure? Example use cases in JavaScript and React
Matt
Matt

Posted on • Updated on

What is a closure? Example use cases in JavaScript and React

What is a closure?

If you are not completely new to JavaScript and are unfamiliar with closures, you have probably used a closure without knowing it. A closure is when a function has access to variables (can read and change them) defined in its outer scope, even when the function is executed outside of the scope where it was defined. A closure is a function enclosing a reference (variable) to its outer scope. Functions can access variables outside of their scope.

Here is a simple example where an outer function that returns an inner function has access to a variable in the outer function:

function outerFunction() {
  let outerFuncVar = "outside";
  function innerFunction() {
    console.log(`The value is: ${outerFuncVar}`);
  }
  return innerFunction();
}

outerFunction();
Enter fullscreen mode Exit fullscreen mode

Console output: The value is: outside

 

The outer function returns an inner function that "closes" over the outer function variable outerFuncVar. This is why it is called a closure. The outerFunction, which returns the innerFunction, can be called anywhere outside of its scope and the innerFunction will have access to, it can remember, the outerFuncVar. When it is called, it can read the value of this variable.

Let's modify the above example so that the outerFunction variable can be changed and new value is logged after 5 seconds has passed:

function outerFunction(input) {
  let outerFuncVar = input;
  function innerFunction() {
    setTimeout(() => {
      console.log(`The value is: ${input}`);
    }, 5000);
  }
  return innerFunction();
}

outerFunction("new value");
Enter fullscreen mode Exit fullscreen mode

Console output: The value is: new value

 

Even after outerFunction has finished executing in the above example, the outerFuncVar is still accessible 5 seconds after the function was called. JavaScript automatically allocates memory when variables are initially declared. After a function returns, its local variables may be marked for garbage collection and removed from memory. Garbage collection is a type of automatic memory management used by JavaScript to free memory when an allocated block of memory, such as a variable and its value, is not needed anymore.

If the outerFuncVar was garbage collected right after the function call, it would cause an error because the outerFuncVar would no longer exist. The outerFuncVar is not garbage collected because JavaScript works out that the nested innerFunction may still be called as it is used in a closure. JavaScript does memory management for us, unlike low-level languages such as C.

You can also see this persistence of the closures reference to an outer variable by returning the innerFunction from the outerFunction and storing it in a variable before executing the innerFunction:

function outerFunction() {
  let outerFuncVar = "outside";
  function innerFunction() {
    console.log(`The value is: ${outerFuncVar}`);
  }
  return innerFunction;
}

const innerFunct = outerFunction();
innerFunct();
Enter fullscreen mode Exit fullscreen mode

Console output: The value is: outside

 

If the outer function is a nested function itself , such as outerOuterFunction in the code below, all of the closures will have access to all of their outer function scopes. In this case the innerFunction closure has access to the outerFunction and outerOuterFunction variables:

function outerOuterFunction() {
  let outerOuterFuncVar = "outside outside";
  return function outerFunction() {
    let outerFuncVar = "outside";
    function innerFunction() {
      console.log(`The outerFunction value is: ${outerFuncVar}`);
      console.log(`The outerOuterFunction value is: ${outerOuterFuncVar}`);
    }
    return innerFunction;
  };
}

const outerFunct = outerOuterFunction();
const innerFunct = outerFunct();
innerFunct();
Enter fullscreen mode Exit fullscreen mode

Console output:
The outerFunction value is: outside
The outerOuterFunction value is: outside outside

 

Multiple instances of a closure can also be created with independent variables that they close over. Let's look at a counter example:

function counter(step) {
  let count = 0;
  return function increaseCount() {
    count += step;
    return count;
  };
}

let add3 = counter(3); // returns increaseCount function. Sets step and count to 3
let add5 = counter(5); // returns increaseCount function. Sets step and count to 5

add3(); // 3
console.log(add3()); // 6

add5(); // 5
add5(); // 10
console.log(add5()); // 15
Enter fullscreen mode Exit fullscreen mode

 

When the counter function is called using counter(3), an instance of the increaseCount function is created that has access to the count variable. step is set to 3, it's the function parameter variable, and count is set to 3 (count += step). It is stored in the variable add3. When the counter function is called again using counter(5), a new instance of increaseCount is created that has access to the count variable of this new instance. step is set to 5 and count is set to 5 (count += step). It is stored in the variable add5. Calling these different instances of the closure increments the value of count in each instance by the step value. The count variables in each instance are independent. Changing the variable value in one closure does not effect the variable values in other closures.

 

A more technical definition of a closure

A closure is when a function remembers and has access to variables in its lexical / outer scope even when the function is executed outside of its lexical scope. Closures are created at function creation time. Variables are organized into units of scope, such as block scope or function scope. Scopes can nest inside each other. In a given scope, only variables in the current scope or at a higher / outer scope are accessible. This is called lexical scope. Lexical, according to the dictionary definition, means relating to the words or vocabulary of a language. In this case, you can think of it as how scoping occurs in the JavaScript language. Lexical scoping uses the location of where a variable is declared in the source code to determine where the variable is available in the source code. Scope is determined at compile time, more specifically lexing time, by the compiler of the JavaScript Engine used to process and execute the code. The first stage of compilation involves lexing / parsing. Lexing is when the code is converted into tokens, which is part of the process of converting code into machine readable code. You can read about how the JavaScript engine works in this article: JavaScript Visualized: the JavaScript Engine.


 

Why are closures important? Some examples

Here are a few examples of where closures are used in JavaScript and React.

 

JavaScript

Async code

Closures are commonly used with async code, for example: sending a POST request using the Fetch API:

function getData(url) {
  fetch(url)
    .then((response) => response.json())
    .then((data) => console.log(`${data} from ${url}`));
}

getData("https://example.com/answer");
Enter fullscreen mode Exit fullscreen mode

When getData is called, it finishes executing before the fetch request is complete. The inner function fetch closes over the url function parameter variable. This preserves the url variable.

 

Modules

The JavaScript module pattern is a commonly used design pattern in JavaScript to create modules. Modules are useful for code reuse and organization. The module pattern allows functions to encapsulate code like a class does. This means that the functions can have public and private methods and variables. It allows for controlling how different parts of a code base can influence each other. Closures are required for this, for functional modules. Functional modules are immediately invoked function expressions (IIFE). The IIFE creates a closure that has methods and variables that can only be accessed within the function, they are private. To make methods or variables public, they can be returned from the module function. Closures are useful in modules because they allow module methods to be associated with data in their lexical environment (outer scope), the variables in the module:

var myModule = (function () {
  var privateVar = 1;
  var publicVar = 12345;

  function privateMethod() {
    console.log(privateVar);
  }

  function publicMethod() {
    publicVar += 1;
    console.log(publicVar);
  }

  return {
    publicMethod: publicMethod,
    publicVar: publicVar,
    alterPrivateVarWithPublicMethod: function() {
      return privateVar += 2;
    },
  };
})();

console.log(myModule.publicVar); // 12345
console.log(myModule.alterPrivateVarWithPublicMethod()); // 3
myModule.publicMethod(); // 12346
console.log(myModule.alterPrivateVarWithPublicMethod()); // 5
console.log(myModule.privateVar); // undefined
myModule.privateMethod(); // Uncaught TypeError: myModule.privateMethod is not a function
Enter fullscreen mode Exit fullscreen mode

 

Functional programming - currying and composition

Currying a function is when a function that takes multiple arguments is written in such a way that it can only take one argument at a time. It returns a function that takes the next argument, which returns a function that takes the next argument, ... this continues until all of the arguments are provided and then it returns value. It allows you to break up a large function into smaller functions that each handle specific tasks. This can make functions easier to test. Here is an example of a curried function that adds three values together:

function curryFunction(a) {
  return (b) => {
    return (c) => {
      return a + b + c;
    };
  };
}
console.log(curryFunction(1)(2)(3)); // 6
Enter fullscreen mode Exit fullscreen mode

 

Composition is when functions are combined to create larger functions, it is an important part of functional programming. Curried functions can be composed into large, complex functions. Composition can make code more readable because of descriptive function names. The following is a simple example of currying and composition where there are two number functions (for simplicity): five and six that use the n function, which allows them to be called alone or composed with other functions such as the plus function. The isEqualTo function checks if two numbers are the same.

var n = function (digit) {
  return function (operator) {
    return operator ? operator(digit) : digit;
  };
};

var five = n(5);
var six = n(6);

function plus(prev) {
  console.log('prev = ', prev); // prev = 6
  return function (curr) {
    return prev + curr;
  };
}

function isEqualTo(comparator) {
  console.log('comparator = ', comparator); // comparator = 5
  return function (value) {
    return value === comparator;
  };
}

console.log(five()); // 5

// values calculated from the inside to the outside
// 1. six() => result1
// 2. plus(result1) => result2
// 3. five(result2) => final result
console.log(five(plus(six()))); // 11
console.log(isEqualTo(five())("5")); // false
Enter fullscreen mode Exit fullscreen mode

 

You can read more about currying and composition in this article: How to use Currying and Composition in JavaScript.

 

Here is an example of a debounce function, from https://www.joshwcomeau.com/snippets/javascript/debounce/, that returns a function and makes use of a closure, like the counter example that we used earlier:

const debounce = (callback, wait) => {
  let timeoutId = null;
  return (...args) => {
    window.clearTimeout(timeoutId);
    timeoutId = window.setTimeout(() => {
      callback.apply(null, args);
    }, wait);
  };
};
Enter fullscreen mode Exit fullscreen mode

 

Modern front end frameworks/libraries like React make use of a composition model where small components can be combined to build complex components.

 

React

Making hooks

Here is a function that mimics the useState hook. The initial value, the state getter, is enclosed in the closure and acts like stored state:

function useState(initial) {
  let str = initial;
  return [
    // why is the state value a function? No re-render in vanilla JavaScript like in React.
    // if you just use the value (no function), then change it with the setter function(setState) and then the log value, it will reference a "stale" value (stale closure) -> the initial value not the changed value
    () => str,
    (value) => {
      str = value;
    },
  ];
}

const [state1, setState1] = useState("hello");
const [state2, setState2] = useState("Bob");
console.log(state1()); // hello
console.log(state2()); // Bob
setState1("goodbye");
console.log(state1()); // goodbye
console.log(state2()); // Bob
Enter fullscreen mode Exit fullscreen mode

To see a better implementation where the state value is not a function check out the following article - Getting Closure on React Hooks.

 

Closures remember the values of variables from previous renders - this can help prevent async bugs

In React, if you have an async function that relies on props that may change during the async function execution, you can easily end up with bugs if you use class components due to the props value changing. Closures in React functional components make it easier to avoid these types of bugs. Async functions, that use prop values, use closures to preserve the prop values at the time when the function was created. Each time a component renders, a new props object is created. Functions in the component are re-created. Any async functions that use variables from the props (or elsewhere), remember the variables due to closure. If the component that an async function is in is re-rendered and the props change (new values) during the async function call, the async function call will still reference the props from the previous render, where the function was defined, as the values were preserved due to closure. You can see an example of this in the article - How React uses Closures to Avoid Bugs.


 

Conclusion

We learned what closures are using some examples and saw some example use cases in JavaScript and React. To learn more about closures, you can check the articles linked below.


 

References / Further Reading

Top comments (2)