Back
Thu Jan 26 2023
Understanding JavaScript Closures

image

Hey there! 👋

Closures in JavaScript can be a tricky subject to understand at first. It's also common to be asked about closures in interviews, which is why this post aims to explain how closures work in JavaScript.

What is a closure?

According to mozilla.org, A 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.

Let's take a look at an example of a basic closure:

let global = 10;
 
function print() {
  const local = 5;
  console.log(local + global);
}
 
print(); // prints 15

In this example, the print() function has access to the global and local variables in its surrounding scope. This is what we call lexical scoping.

Now, let's see what happens if we change the value of global after the function declaration:

let global = 10;
 
function print() {
  const local = 5;
  console.log(local + global);
}
 
global = 5; // new value
print(); // prints 10

Here, the print() function still has access to the global, even though it was changed after the function was created. This is the power of closures in JavaScript.

In simple terms, closure refers to the ability of an inner function to access variables in the scope of an outer function, even after the outer function has finished executing. This is achieved by the inner function "remembering" the variables from the scope where it was created. Closures are created at the time a function is defined, not when it is called.

This seems pretty simple so far, now let's look at a more concrete example:

function example() {
  const num = 5;
 
  // here we are creating a closure
  function printNum() {
    console.log(num);
  }
  return printNum;
}
 
const closureFunction = example();
closureFunction(); // prints 5

In this example, example() function defines a variable num and an inner function printNum that has access to that variable num from the example() function scope even when the example() function has finished executing. We return printNum() function and store it in a variable closureFunction, so we can call it later and it will still have access to the num variable and print its value.

In other programming languages, the variable num would no longer be accessible and may be eligible for garbage collection after the example() function has finished executing. However, in JavaScript, the inner function printNum creates a closure, which "remembers" the variable num from the outer function's scope and allows it to be accessed even after the outer function has finished executing. This means that the variable num will not be eligible for garbage collection until the closure (i.e. the inner function printNum) is no longer being used.

What can we do with Closures?

Private Methods

Many useful things can be done with closures, one of the most popular is creating private methods.

Let's take a look at the following example:

function createCounter() {
  let count = 0;
  return {
    increment: function () {
      count++;
    },
    getCount: function () {
      return count;
    },
  };
}
 
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
console.log(counter.getCount()); // 1

In this example, we have a createCounter function that returns an object containing two methods, increment and getCount. The increment method increments the value of a private variable count, which is only accessible within the scope of the createCounter function.

The getCount method returns the current value of count. By returning an object containing these methods rather than the value of count, we have created a private variable that can only be accessed or modified through the provided methods, this is a common use case of closures.

We can also create multiple instances of createCounter function.

function createCounter() {
  let count = 0;
  return {
    increment: function () {
      count++;
    },
    getCount: function () {
      return count;
    },
  };
}
 
const counter1 = createCounter();
console.log(counter1.getCount()); // 0
counter1.increment();
console.log(counter1.getCount()); // 1
 
const counter2 = createCounter();
console.log(counter2.getCount()); // 0
counter2.increment();
console.log(counter2.getCount()); // 1

This allows for better encapsulation and data hiding, making the code more maintainable and scalable.

Function Factories

Another use of closures is to create function factories. A function factory is a function that returns a new function with a specific behavior. The returned function has access to variables in the scope of the function factory, which allows the factory to customize the behavior of the returned function.

For example, let's say we want to create a function that can multiply a number by a specific factor. We can use a closure to create a function factory that takes a factor as an argument and returns a new function that multiplies its input by that factor:

function createMultiplier(factor) {
  return function (num) {
    return num * factor;
  };
}
 
const doubler = createMultiplier(2);
console.log(doubler(5)); // 10
 
const tripler = createMultiplier(3);
console.log(tripler(5)); // 15

In this example, we have created two instances of doubler and tripler using the createMultiplier. Each instance has its own copy of the factor variable and its own behavior to multiply its input by that factor.

Bonus 🎁

This last section of this post is a way to test your understanding of closures. It is also very common to see this example in technical interviews, so let's take a closer look.

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 500);
}

If we run this code, we will get 1, 2, and 3 printed in the console. Each iteration creates a closure with a new version of the variable let, but what happens if we change let to var?

Let's see an example:

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 500);
}

If we run this code, we will get "3, 3, 3" printed in the console. The reason is that var is function scoped (i.e. global scoped), so the variable is shared among all the closures, and by the time the callbacks are invoked, the variable has a value of 3.

This is an important difference between let and var we are reusing the same variable so the value is updated by the time the callback is invoked but with let we are creating a copy on each iteration with the updated value.

Conclusion

Closures are a very interesting topic in JavaScript, and is fundamental to have a good understanding of how they work.

I hope that by now when you get asked a tricky question in an interview you respond elegantly and knowledgeably.

If you like this post please consider following me:

Github

Medium

Twitter