JavaScript closures: What are they?

A closure in JavaScript is a pretty obscure concept to grasp, especially when starting out. They’re one of the more important building blocks and concepts in JS that enable many other features in the language to work. (Like iterators and generators, and even paves the way for concepts like observable streams)

From MDN, a closure is as follows:

closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Essentially, we can think of a closure as a backpack that every function gets whenever it is defined. The contents of said backpack are dependent on where the function was defined – this is also known as its scope. This is what we mean when something is lexically scoped. In this backpack we will find the surrounding function’s variables, which will allow the inner function (the one that is carrying the backpack) to have access to the outer function’s scope/variables even after it’s finished executing. We can really start to leverage this feature of JavaScript when we return a function from another function.

Let’s take a look at a really basic example to get a better understanding at how this concept works in practice:

function outerFunc() {
  let counter = 0;

  return function innerFunc() {
    console.log(`The value of counter is: ${counter}`);
    counter++;
  };
}

const runnerFunc = outerFunc();

runnerFunc();
runnerFunc();
runnerFunc();
runnerFunc();
runnerFunc();
runnerFunc();

And the output that’s logged to the console is:

The value of counter is: 0
The value of counter is: 1
The value of counter is: 2
The value of counter is: 3
The value of counter is: 4
The value of counter is: 5

Line 6 from the code example above (Not the console.log output) is extremely important – the inner function is incrementing a variable that was defined in the outer function’s scope. When running functions, we’re used to them not being able to persist the variables/state from its previous execution context (the last time it was run), but somehow in this example the inner function was still able to access variables that belonged to the outer function’s scope even after it was finished running! Enter the concept of closures!

We can do some really interesting things with this newly found knowledge. Let’s take another look at a custom made generator function that we can pull values from one at a time. This is a big paradigm shift in that we normally iterate over a collection of objects and get the value one at a time that way using a tried and true for or while loop. Let’s have a look:

function countUpUntil(x) {
  let counter = 0;
  function next() {
    if (counter <= x) {
      return { value: counter++, done: false };
    }
    return { value: undefined, done: true };
  }

  return { next };
}

const iterator = countUpUntil(5);

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(interator.next());

And let’s have a look at the output from the console:

{ value: 0, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: undefined, done: true }

Now we’ve created a function in such a way that will only allow us to run it a certain amount of times! We can use this method to enable us to “pull” from a collection each time the function is run as opposed to us traversing the entire collection using a traditional for/while loop – an incredible paradigm shift that paves the way for concepts like observable streams, generator functions, and iterator functions, all of which are significant building blocks in the JavaScript ecosystem.

In the above code example, we again have an inner function, this time called next, that is accessing the variable with the label counter that was defined in the outer function’s execution context. In fact, we reference counter twice – we perform a conditional to see if counter is less than or equal to x (the number we want to ‘count’ up until), and if the conditional is true, we then return an object that contains the value of count after it’s been incremented by 1, as well as a boolean letting us know whether or not the stream is done. When the conditional returns false, we get an object back with a value of undefined, and a truthy value for done letting us know the stream has finished giving us results. We can then implement logic elsewhere in our program to check for the truthy value.