Understanding JavaScript closures with simple examples

Reading Time: 4 minutes 🕑

Last updated: May 7, 2022.

JavaScript closures are a way of enabling a function to have ‘private’ variables that cannot be accessed outside it.

Creating a closure is a better-practice alternative to creating a global variable.

This is because the variables contained in a closure are only modifiable by calling the closure function. A globally defined variable, on the other hand, can be modified by any code in your script.

Table of contents

Closure: A simple example

Creating a counter

Let’s create a closure that could replace a global variable called index.

Consider the following code:

function createIndex() {
  let index = 0;

  function increaseByOne() {
    index++;
    return index;
  }
  
  return increaseByOne;
}

We are creating a function: createIndex(). And, inside, we are creating an index variable, set to the value 0.

Then, we are defining a function within the function and finally returning a reference to the inner-function.

So now, when we call createIndex(), it will return a reference to the inner-function.

If we do this and store the reference value in a variable (so that it is now a function expression), we can now call the inner-function by calling this function expression:

function createIndex() {
  let index = 0;

  function increaseByOne() {
    index++;
    return index;
  }
  
  return increaseByOne;
}

const closure = createIndex();
closure() // 1
closure() // 2
closure() // 3

This works because in JavaScript, a function has access to variables defined in a parent function in which it is called, even after the parent function has finished executing and is 'closed'.

So now, once createIndex is called, the index variable it creates can only be accessed by calling closure(), which has stored in it a reference to the inner-function increaseByOne().

Adding more than one inner function

One of several inner functions can be executed when calling the closure function by making the return value of createIndex() an inner function that calls one of several other functions.

Which inner function is called can then be made conditional upon a parameter value passed in to the closure function when it is called (since the return value is now the parameter-accepting function).

For example, below the return value of createIndex() is now a reference to performAction(). So calling the function output by createIndex() feeds a value into performAction(), which then calls either increaseByOne(), decreaseByOne() or returnValue().

function createIndex() {
  let index = 0;

  function increaseByOne() {
    index++;
    return index;
  }

  function decreaseByOne() {
    index--;
    return index;
  }

  function returnValue() {
    return index;
  }

  function performAction(action) {
    if (action === -1) {
        decreaseByOne()
    } else if ( action === 1) {
        increaseByOne()
    } else if ("returnValue") {
        returnValue()
    }
    return index;
  }
  
  return performAction;
}

const closure = createIndex();
closure(-1)
closure(-1)
const currentValue = closure("returnValue");
console.log(currentValue); // -2

Creating a closure with a self-executing function

When only a single closure is needed, it is not uncommon for these to be created using a self-executing function or IIFE (immediately-invoked function expression).

The code below produces a closure identical to the simple counter example:

const closure = (function createIndex() {
  let index = 0;

  function increaseByOne() {
    index++;
    return index;
  }
  
  return increaseByOne;
})()

closure()
closure()
console.log(closure()) // 3

One advantage of this solution is that createIndex() is not declared in the global scope, and so does not take up this namespace throughout the rest of your script:

const closure = (function createIndex() {
  let index = 0;

  function increaseByOne() {
    index++;
    return index;
  }
  
  return increaseByOne;
})()

function anotherTask () {
    function createIndex() { // No error!
        let index = 0;
    }
}

Why are closures useful?

Avoiding unintended conflicts in your own code

Imagine you do not use a closure and instead create index as a global variable.

One way that this can backfire is that the commonly used name index is now used for the global variable. Use of this name elsewhere in your code can result in an error or, worse, an unexpected change in the value of index that is hard to debug.

/* Defining a global variable can result in an unwanted conflict with other parts of your code */

let index = 0;

function increaseByOne() {
   index++;
   return index;
}

increaseByOne() // 1
increaseByOne() // 2

// Elsewhere in your code:

function createImageCarousel() {
    const images = ["img1.png", "img2.png", "img3.png"];
    let index = 0;
    setInterval(() => {
        index++
    }, 1000)
}

createImageCarousel()

// Print the value of index every second:
setInterval(() => {
        index++
        console.log(index); // 3, 4, 5, 6, 7, 8, etc.
    }, 1000);

Avoiding conflict with third-party scripts

You may be able to keep track over your own code to avoid conflicts, but sometimes you may use a third-party script in your project.

If the third-party script needs to create a variable called index and is loaded on the same page as your script, there will be a conflict between them. This can be really hard to debug as you may not have access to the third-party script.

However, if you create index using a closure, this minimizes the possibility of a conflict.

Summary

A closure is the return value of an inner function. In JavaScript, inner functions have access to variables created in their outer functions, even after the outer function has closed.

Taking advantage of this fact, it is possible to create functions with 'private' variables that cannot be accessed by anything other than the inner function. This is perfect for protecting global variables.

Related links