A deep dive into JavaScript closures

    Queen Nnakwue

    Queen Nnakwue is a technical writer on a mission to make learning the hard stuff fun by writing in the easiest way possible.

    Published on

    March 7, 2022
    A deep dive into JavaScript closures

    Understanding closures and how they work can allow for a deeper understanding of JavaScript and other programming languages we use that heavily rely on this concept. This explains why so much emphasis is placed on the need to understand closures.

    In this article, we will be introducing JavaScript Closures from the ground up. We will start by looking at closures from a high level - what are they, how they work, how to use them in our code, and how they are structured.

    We will also look at the scope chain and its use cases. Lastly, we will explore how to emulate private methods with closures effectively.

    Prerequisites: To follow through with this tutorial, a basic understanding of the JavaScript function scope and familiarity with the lexical environment is necessary. On the flip side, we will also cover the basics of these, so readers of all background levels can follow suit.

    What is a closure?

    A Closure is a function (an inner function) that has access to another function (an outside function) outside of its scope. It is a function combination created by calling a function inside another function. Closures are used widely in Nodes' single-threaded, event-driven, and non-blocking architecture.

    Let us illustrate how this works with an example below:

    function greeting(){
        let message = "Hello World";
        function displayMessage(){
        return displayMessage;
    let newMessage = greeting();

    In the above example, greeting() creates a local variable called message and an inner function called displayMessage(), defined inside greeting() and is available only within the scope of the greeting() function. However, it will interest you to note that displayMessage() can access the variable message declared in its parent function, greeting().

    Let’s try this in real-time. Go to your dev tools in your browser and run the above lines of code. Notice that “Hello World” is passed to alert? The reason isn’t far-fetched. Recall we mentioned at the beginning that closures are a combination of functions that have access to each other within the same lexical environment. Following that definition, newMessage refers to the instance of the function displayMessage that is created when the greeting() function is run.

    Note: The instance of displayMessage maintains a reference to its lexical environment, within which the variable message exists.

    For this reason, when we invoke newMessage, the variable message remains available for use, and "Hello World" is passed to alert.

    Using closures in your code

    One of the essential features of closures is that the inner function still has access to and can use the outer function's variables even after the outer function has run or returned.

    In JavaScript, functions execute using the same scope chain that was in place when they were created. This means that even after the outer function has returned, the inner function still has access to the outer function’s variables.

    This example throws more light:

    function firstName (firstName) {
      var nameIntro = "My name is ";
      // this is an inner function which as we will get to see, has access to the outer function's variables
    //this function lastName is a closure because it is a function inside another  function)
       function lastName (lastName) {
            return nameIntro + firstName + " " + lastName;
        return lastName;
    var fullName = firstName("Bill");
    //The firstName outer function has returned which we passed to this var fullName
    fullName ("Johnson");  // My name is Bill Johnson
    // The closure (lastName) is called after the outer function has returned above
    // Yet, the closure still has access to the outer function's //variables and parameter

    Further, let's also explore how we can use closure to define a function (an inner function) n inside another function m (an outer function) and expose n. Let us see how we will do so below with a quick example:

    function multiply (m) {
      return function (n) {
        return m * n
    var multiply9 = multiply(9)
    var multiply5 = multiply(5)
    console.log (multiply9(8)); // 72
    console.log (multiply5(12)); // 60

    We defined a multiply(m) function, which takes a single argument, m, and returns a new function n. The function it returns, in turn, takes a single argument, n, and returns m  * n. multiply is used to create two new closures (functions) — multiply9  and  multiply5 which contain different lexical scopes although they share the same function body definition. For instance, In the lexical scope of multiply9, m is 9 while in multiply5, m is 5.

    Why do we need closures?

    Closures are essential as they can control what is and isn’t in the scope of a particular function. They also control which variables sibling functions share in the same lexical scope. Closures can also help simplify our code and make our lives as programmers easier.

    For example, we can use closure to store and hide data in a separate scope and share it only when we deem fit — data encapsulation. However, when we use closures for data encapsulation, the enclosed variables are only within the scope of the containing (outer) function. This means that we can’t access the data from an outside environment except through the methods defined in the object.

    function createSquare(side) {
        return {
            area: function() {
                return side * side;
    let square = createSquare(7);
    console.log("Area is " + square.area());
    console.log("Diameter is " + square.side); // THIS WILL NOT RUN

    In the example above, side is inside the scope of the function createSquare, and it’s only visible inside the scope of the function createSquare. This is because variables/parameters are visible only inside its function.

    It's also accessible inside the area function since that function is defined inside the function createSquare. This makes area exposed but Diameter is hidden, meaning that the circle encapsulates the side within it.

    This is a prevalent use case that we will see in practice and real-world use cases without even knowing its closure in place.

    Additionally, closures are also helpful in associating data with a function that operates on that data. This is similar to object-oriented programming, where an object effectively associates the same object's properties with one or more methods.

    Understanding the scope chain

    In JavaScript, every closure has three scopes, namely:

    • Global
    • Function scope/local scope
    • Block

    Now let's explore each of them below.

    Global Scope

    When a variable defined is not inside a function or a pair of curly braces, { } then we can say that such a variable exists in the Global scope and therefore can be accessed from anywhere in the program. For example:

    var greeting = 'Welcome Home';
    //greeting is a global variable that we can access from anywhere
    function greet() {
    greet(); //We get "Welcome Home" printed on the console because the greet function can access the global variable

    Function scope/local scope

    When variables are declared inside a function, we can only access them from within that function, which means we can't access them outside the function scope. For example:

    function greet() {
      var greeting = 'Welcome Home';
    // Prints 'Welcome Home'
    greet(); //try calling the function outside of its scope==>Uncaught ReferenceError: greeting is not defined

    Block Scope

    With ECMAScript 2015 (or ES6), let and const keywords, variables can only be scoped to the nearest pair of curly braces. Unlike var variables, we can't access them outside that pair of curly braces. For example:

      let greeting = 'Welcome Home';
      var course = 'JavaScript';
      console.log(greeting); // Prints 'Welcome Home'
    // Prints 'JavaScript'
    // Uncaught ReferenceError: greeting is not defined

    Emulating private methods with closures

    Private methods/functions in JavaScript are only accessible within their scope. These variables also allow the function defined within their scope to have total control over how they are manipulated.

    JavaScript does not give us the luxury to specify whether variables and methods associated with objects should be public or private in their declaration. Still, it does give us a way to implement them, as seen below:

    var counter = (function() {
      var privateCounter = 0;
      function changeBy(val) {
        privateCounter += val;
      return {
        increment: function() {
        decrement: function() {
        value: function() {
          return privateCounter;
    console.log(counter.value());  // 0.
    console.log(counter.value());  // 2.
    console.log(counter.value());  // 1.

    Source: MDN

    In the code block above, there is a single lexical environment- counter that is shared by the three functions: counter.increment, counter.decrement, and counter.value. The lexical environment contains two private items: a variable called privateCounter, and a function called changeBy. We cannot access these private members from outside the anonymous function counter. Instead, you can access them using the three public functions returned from the anonymous wrapper. The three public functions are closures that share the same lexical environment and can each access the privateCounter variable and the changeBy function through scoping.


    • Whenever you have a function inside another function, then you have a closure.
    • Closures (that inner function or function returned by another function) have a reference to their lexical environment during the time of their creation.
    • Closures can help you create private data, whether it’s a private method or a private variable.
    • Every closure has three scopes: global, local, and block Scope.

    Further reading

    Data-rich bug reports loved by everyone

    Get visual proof, steps to reproduce and technical logs with one click

    Make bug reporting 50% faster and 100% less painful

    Rating LogosStars
    Category leader

    Liked the article? Spread the word

    Put your knowledge to practice

    Try Bird on your next bug - you’ll love it

    “Game changer”

    Julie, Head of QA


    Overall rating: 4.7/5

    Try Bird later, from your desktop