Javascript Closures Made Easy

A Deep Dive Into Javascript Closures. Explained in Simple English, With Practical Examples

Published by Carlo van Wyk on March 29, 2022
Javascript Closures

Why should you learn closures?

  • Encapsulation - closures can be used for encapsulation. In other words, to hide variables of a function and selectively expose methods
  • Init functions - closures can be used to ensure that a function is only called once
  • Memory optimization - closures can be used to make functions more memory efficient and performant - for example, ensure that a large array or object is only initialized once
  • Functional programming - closures are a fundamental concept of functional programming. Without closures, higher-order functions and currying is not possible
  • Interviews - questions around closures are almost guaranteed in most javascript interviews. Understanding how closures work will allow you to answer any variation of a closure question with ease

What is a closure?

I'm going to give a definition of a closure, and it's not going to make any sense at all. And then I'll follow up with a few practical examples, and once we've gone through a practical example, the definition will make perfect sense.

Closure Definition

A closure is when an outer function returns an inner function, the inner function is then executed in a different scope, and the inner function continues to maintain access to the outer function's variables, even though the outer function no longer exists.

Watch the Youtube Video

 

Closure Example 1: Simple Closure

function greet() {
  const name = 'John';

  return function () {
    console.log(`Hi ${name}`);
  };
}

const greeting = greet();

greeting();

 

Output:

Hi John

 

At face value, you might think: "what's the big deal?".

The outer function on line 1 is returning an inner function on line 4, and the inner function is simply logging out a greeting to the console.

However, if you analyze the function in more detail, you might notice some odd behaviour.

For instance, on line 5, the inner function is referencing the name parameter that is in the outer function.

This in itself is not entirely unusual, because this is simply making use of lexical scope, which is to say that a function can access a variable that was defined outside of that function.

But then if you look at line 9, you'll see that I declared a variable called greeting, and this variable holds the inner function that was returned on line 4. In other words, the inner function has returned and the outer function no longer exists.

Moreover, at the time that the inner function is invoked, it references the outer function's name parameter even though the outer function no longer exists.

So how is the inner function able to reference a parameter in the outer function if the outer function no longer exists?

The simple answer is closures.

A closure allows the inner function that is returned access to all of the variables in the outer function which the inner function references, even though the outer function would have been destroyed by the time that the inner function is invoked.

Let's have a look at the closure definition again, and this time it should make a bit more sense:

A closure is when an outer function returns an inner function, the inner function is then executed in a different scope, and the inner function continues to maintain access to the outer function's variables, even though the outer function no longer exists. 

Closure Example 2: Count Function

function setCount() {
  let number = 0;

  return function () {
    console.log(++number);
  };
}

const counter = setCount();
counter();
counter();
counter();

 

In this example, I've got a setCount function on line 1 with a number variable on line 2 which is set to 0.

On line 4 an anonymous inner function is returned which increments the number variable by 1.

Then, on line 9 a counter variable is declared which holds the result of the setCount function invocation, and the result of the setCount is the inner function that is returned on line 4, so once again, counter is a function and not a value.

And finally, on lines 10, 11 and 12, the counter function is invoked 3 times.

Before I run this script, what do you think the output would be?

Let's run the script and see.

Output:

1
2
3

Why is the result 1, 2 and 3?

Because the inner function increments the number on line 4, the closure allows us access to the number variable that was declared even though the outer function was destroyed.

Each time the counter function is invoked on lines 10 to 12, the value of the number variable on line 2 is incremented.

This example clearly demonstrates the concept of a closure. Even though the outer function no longer exists, the inner function continues to have access to the outer function's variables.

Closure Example 3: For Loop Interview Question

function addNumbers() {
  var numbers = [];

  for (var i = 1; i <= 3; i++) {
    numbers.push(function () {
      return i;
    });
  }

  return numbers;
}

const getNumbers = addNumbers();

console.log(getNumbers[0]());
console.log(getNumbers[1]());
console.log(getNumbers[2]());

The above example is quite common in interviews, so take your time to ensure you understand this example.

What do you expect the output for the above example to be?

If you expected the output to be 1, 2 and 3, you'd be wrong.

Output:

4
4
4

Why is the result 4, 4, 4?

The for loop uses a var variable, and a var that is used in a for loop in javascript is hoisted. What this means is that when the code is executed, the javascript interpreter creates a var i declaration on line 3. In other words, the i variable is declared outside of the for loop, and the variable is then mutated during each iteration.

This means that after the for loop runs to completion, the i variable will be equal to 4. Why 4?

Think about how a for loop works.

A simple for loop example is provided below:

for (var i = 1; i <= 3; i++) {
  console.log(i);
}

console.log(`i after for loop ran to completion: ${i}`);

Output:

1
2
3
i after for loop ran to completion: 4

Many of us use a simple for loop on a daily basis without truly understanding how it works.

If you have a look at the for loop example on line 1, you'll see that the for loop takes 3 expressions inside parenthesis. The first one is the initial or initializer expression, and what it does is to initialize the for loop. The initial expression will only run once, and in this case it creates an i variable and sets it to 1, which is the starting position of the loop.

The second expression is called the condition expression. This expression will evaluate each pass of the loop. It will evaluate to true or false, and as long as this expression evaluates to true, the for loop will continue running. If the expression evaluates to false, the for loop terminates. So here you can see the for loop will keep running as long as i is smaller or equal to 3.

The last expression is the increment expression. It will increment the i by 1 after each pass through the loop.

So even though the for loop only does 3 passes, it will still increment the value of i after the last pass, because that's how the increment expression works.

We now know what the value of i will be after the for loop runs to completion, but that still doesn't explain why each of the values returned from the getNumbers array on lines 15 to 17 is 4, 4, 4 instead of 1, 2 and 3, as one might expect.

To understand this let's go back to line 5 in the for loop.

For each iteration, we're pushing an anonymous function to the array. If that sounds odd, it's not. You can push a value, an object or a function to an array.

If we pushed i to the array, we would push the value of i at the time of iteration to the array.

Because we're pushing an anonymous function to the array, th anonymous function will return the value of i at the time of invoking this anonymous function in the array. And at the time of invoking this anonymous function on lines 15 - 17, the loop would have already run to completion.

And by the time that the loop has run to completion, i will be equal to 4 because of the way that the increment expression in a for loop works.

The only remaining question is how can each anonymous function in the getNumbers array access the value of i?

Think about what's happening when we execute the anonymous function on line 15.

  • We're invoking an anonymous function from the global scope on line 15
  • addNumbers on line 1 is an outer function that returns a bunch of inner functions, because the numbers array that is returned on line 10 contains a bunch of anonymous functions that are essentially an array of inner functions in the addNumbers function
  • each of these inner functions in the numbers array is accessing the i variable which is in the addNumbers function scope
  • at the time that each inner function is invoked, the addNumbers function no longer exists, yet each of the inner functions maintains access to the i variable that is in the addNumbers scope

So all the conditions for a closure have been met.

This is a classic closure, and that's how the anonymous function in the getNumbers array can be invoked from the global scope and return the value of i.

In conclusion, the result is 4, 4, 4 because of various factors that combined:

  • the var in the for loop is hoisted
  • the value of i after the for loop ran to completion is 4 because of the way that the increment expression works
  • invoking each anonymous function returns 4 because the anonymous function returns the value of i at the time of execution
  • each anonymous function has access to the i variable because of closures

Example 4: For Loop with Let

function addNumbers() {
  var numbers = [];

  for (let i = 1; i <= 3; i++) {
    numbers.push(function () {
      return i;
    });
  }

  return numbers;
}

const getNumbers = addNumbers();

console.log(getNumbers[0]());
console.log(getNumbers[1]());
console.log(getNumbers[2]());

Output:

1
2
3

In the previous example, 4, 4 and 4 was returned from the array functions. In a real world example we'd probably want 1, 2 and 3 to be returned.

How can we return 1, 2 and 3 instead?

There's two very simple and easy ways to do this.

The easiest way to do this is by simply using let instead of var in the for loop, as shown in the example above.

When let is used in a for loop, let will have a unique binding for each iteration. Think of this as there being a unique i variable for each iteration.

Example 5: For Loop with IIFE (Immediately Invoked Function Expression)

const addNumbers = () => {
  var numbers = [];

  for (var i = 1; i <= 3; i++) {
    ((index) => {
      numbers.push(() => {
        return index;
      });
    })(i);
  }

  return numbers;
};

const getNumbers = addNumbers();

console.log(getNumbers[0]());
console.log(getNumbers[1]());
console.log(getNumbers[2]());

Output:

1
2
3

The second way that we can achieve a unique binding for each iteration is by using an IIFE or an immediately invoked function.

To do this, we wrap numbers.push in an immediately invoked function expression, as shown in the example above.

By wrapping each numbers.push in an IIFE, we are ensuring that the index variable is privately scoped to this function, and as a result, the index variable will have a unique binding for each iteration.

Closure Example 6: Interview Question - setTimeout

const createCallbacks = () => {
  for (var i = 1; i <= 3; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000);
  }
};

createCallbacks();

Output:

4
4
4

 

This example is a variation of the previous examples we looked at. It's another popular interview question, so I encourage you to watch this so that you can understand how it works because understanding it means that you can answer any variation of this question that comes your way.

Let me explain the reason for the output below:

On line 2 we have a for loop that uses a var. And we know by now that a var in a for loop is hoisted, which means that the i variable is declared above the for loop, and that the i variable is mutated in each iteration.

And the value of i will be 4 after the for loop ran to completion. Why? Because even though the for loop runs from 1 to 3 for a total of 3 iterations, the increment expression runs after each iteration, meaning it will run after the third iteration and set the value of i to 4.

If you're wondering why we're seeing 4, 4 and 4 logged out to the console instead of 1, 2 and 3, the reason is that at the time of the for loop iteration, it's creating 3 setTimeout functions.

Those setTimeout functions won't run at the time of the for loop iteration. The first setTimeout function will run after 1 second, the second one will run after 2 seconds and so on, but the for loop will run to completion in just a few milliseconds.

How does this work?

Well, when the createCallbacks function is invoked on line 9, it will run the for loop. During each for loop iteration we create a setTimeout function. Remember that we're just creating the function. It's not invoked during any of the for loop iterations.

When we create a setTimeout function it is placed on the job queue which is what the javascript framework uses to keep track of when to run jobs.

The createCallbacks function will run to completion almost immediately. Once this happens, javascript will keep running the event loop which keeps running continuously to check if any code needs to execute. It will also check the job queue to see if there's any functions that needs to be executed at a specified time, and if they do, that function will be executed at that time.

The real question is how does each setTimeout function have access to the i variable?

The answer is, you guessed it, closures.

In the first example, I explained how a closure allows an inner function to reference an outer function's variables, even though the outer function no longer exists.

And in this instance, setTimeout is the inner function because it is accessing the i variable which is hoisted outside of the for loop.

It's important to understand that closures are created when functions are created, not when they are invoked. And because a closure was created when this setTimeout function was created, this enables the setTimeout function to access the i variable at whatever time the setTimeout function will run.

Example 7: Encapsulation

const count = () => {
  let count = 0;

  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => console.log(count),
  };
};

const counter = count();

counter.increment();
counter.increment();
counter.increment();
counter.decrement();

counter.getCount();

Output:

2

Now that we're done with most of the theory around how closures work, I'd like to focus on 3 practical examples you can use in your code.

These practical examples are actually a lot easier to understand than the previous examples. Using the closure pattern in your code will solidify your understanding of closures. Moreover, the next time someone asks you how to use closures, you can give them practical use cases.

Think of the following use case: You want to expose a count object with increment, decrement and getCount functions, and each of these functions have access to a private variable that keeps track of the count. In other words, you want to implement encapsulation, which is to say that you want to hide or make an object's state private and only expose methods.

And closures is a simple and effective way to do this.

Let me walk you through the above example.

I've got a count function on line 1, and on line 2 I declared a count variable that is scoped to this function.

Then on line 4 I'm returning an object with an increment function on line 5 that mutates the count variable on line 2 by incrementing it's value.

The decrement function on line 4 decreases the value of count, and the getCount function on line 7 simply logs the value of the count variable out to the console.

Then, on line 11 I create a new variable called counter which is equal to the object that is returned from the count function.

In other words, the counter variable will only contain the object returned from the count function. The count variable on line 2 is not returned from the count function which means that this is a private variable, and only the increment, decrement and getCount functions in the counter object on line 11 has access to the count variable.

This is all made possible through the use of a closure, because as you remember, the definition of a closure is:

A closure is when an outer function returns an inner function, the inner function is then executed in a different scope, and the inner function continues to maintain access to the outer function's variables, even though the outer function no longer exists.

And in this example, we have 1 or more inner functions on lines 5 - 7 that are returned by an outer function on line 1.

These inner functions are invoked in the global scope on lines 13 - 18, and each of these functions continue to have access to the count variable that is defined in the outer function which no longer exists at the time that these functions are invoked.

In the above example, we have successfully used a closure to implement encapsulation in javascript.

Example 8: Using a Closure in an init function to ensure that the function is only executed once 

const init = () => {
  let initialized = false;

  return () => {
    if (initialized) {
      return console.warn('⚠️ init function already called, not initializing');
    }

    initialized = true;
    return console.info('initialized 🚀');
  };
};

const initialize = init();

initialize();
initialize();
initialize();

Output:

initialized 🚀
⚠️ init function already called, not initializing
⚠️ init function already called, not initializing

The above example demonstrates how you can use a closure to ensure that code in an init function is only executed once, regardless of how many times the init function is invoked. This is a very useful pattern with various practical applications. For instance, you could use this to ensure that a database connection is only initialized once.

On line 1 we have the init function on line 1, and on line 2 we have an initialized variable that is set to false.

Then, on line 4 we return a function. And in this function, there's a simple if condition that checks if the initialized variable is set to true or false and acts accordingly.

On line 5, if the init function is true, we log a warning out to the console to say that the code has already been initialized, and nothing else happens.

And if initialized is false, which should be the case whenever the initialize function is invoked for the first time, lines 9 and 10 will run, which will effectively set the initialized variable to true, and any code you need to run such as initializing a database or whatever you want to initialize can run over here.

This is a closure because on line 14 when we set the initialize variable, it is equal to the function on lines 4 to 11 that are returned from the init function on line 1. In other words, we have an inner function that is returned from an outer function, and this inner function references a variable in the outer function that will no longer exist at the time that the inner function is invoked on lines 16 - 18.

Example 9: Inefficient Memory Usage 

const findByIndex = (index) => {
  console.time('array creation');
  const numbers = Array.from(Array(1000000).keys());
  console.timeEnd('array creation');

  const result = numbers[index];

  console.log(`item by index ${index}=${result}`);

  return result;
};

findByIndex(110351);
findByIndex(911234);
findByIndex(520109);
findByIndex(398);

Output:

array creation: 61.145ms
item by index 110351=110351
array creation: 36.785ms
item by index 911234=911234
array creation: 41.395ms
item by index 520109=520109
array creation: 29.857ms
item by index 398=398

The problem with the above function is that every time the findByIndex function is invoked, a new numbers array with a million items are created.

Timings to create the array for each invocation are listed in the output above.

Let's see how we can use closures to ensure that the array is only created when the function is invoked the first time.

Example 10: Efficient Memory Usage

const findByIndex = () => {
  console.time('array creation');
  const numbers = Array.from(Array(1000000).keys());
  console.timeEnd('array creation');

  return (index) => {
    const result = numbers[index];

    console.log(`item by index ${index}=${result}`);

    return result;
  };
};

const find = findByIndex();

find(110351);
find(911234);
find(520109);
find(398);

output:

array creation: 52.937ms
item by index 110351=110351
item by index 911234=911234
item by index 520109=520109
item by index 398=398

In the above example, I changed the function so that it uses closures to ensure that the numbers array is only created once.

We have a findByIndex function on line 1, and then the numbers array is created on line 3.

Then, we return an inner function on line 6 that takes an index parameter, and this index parameter is then used to get the item in the numbers array at the specified index.

On line 15 I create a find function that is equal to the inner function that is returned by the findByIndex function.
When this function is created on line 15, the numbers array will be created.

However, when I invoke the find function on lines 17-20, the array will not be recreated, no matter how many times the find function is called.

Once again, this is done through closures. We have a closure because there is an inner function on line 6 that references a variable defined in an outer function on line 3, and by the time that the inner function is invoked from the global scope on lines 17-20, the outer function no longer exists, yet the inner function still has access to the numbers variable that was defined in the outer function.

And if we look at the script that logs out the time, we can see that the numbers array is only created once, which improves performance.

Conclusion

In this post, I covered everything from what a closure is to practical applications for closures and some of the more challenging interview questions you could face around closures.

While the definition of a closure might be difficult to understand initially, once you demonstrate a closure with a practical example and go back to the definition, everything makes sense.

If you found this tutorial helpful, or if there are any closure concepts you're still struggling with, let me know in the comments below.

Resources