Altcademy - a Forbes magazine logo Best Coding Bootcamp 2023

What are Closures in JavaScript?

Closures in JavaScript can be a somewhat confusing concept for beginners. However, once you grasp the idea, it becomes an incredibly powerful tool in your programming arsenal. In this blog post, we will try to break down closures in a way that is easy to understand, using real-world analogies and code examples.

What are Closures?

In simple terms, a closure is a function that has access to its own scope, as well as the scope of the outer (enclosing) function. This means a closure can "remember" the environment in which it was created, even after the outer function has completed execution.

To better understand closures, let's first discuss some fundamental concepts in JavaScript:

Functions as First-Class Citizens

In JavaScript, functions are considered "first-class citizens." This means that they can be:

  • Assigned to variables
  • Passed as arguments to other functions
  • Returned as values from other functions

This flexibility allows us to create more dynamic and modular code. Closures take advantage of this by allowing us to define a function within another function and then return that inner function for use elsewhere.

Scope

In programming, scope refers to the visibility or accessibility of variables. In JavaScript, there are two types of scope:

  1. Global Scope: Variables declared outside any function or block, accessible from any part of the code.
  2. Local Scope: Variables declared within a function or block, accessible only within that function or block.

Understanding scope is crucial for understanding closures, as closures allow nested functions to access variables from their parent functions even after the parent has finished executing.

Now that we have a basic understanding of functions and scope in JavaScript, let's dive into closures with an example.

Closure Example

Consider a simple example of a closure in action:

function outer() {
  let count = 0;

  function inner() {
    count++;
    console.log(count);
  }

  return inner;
}

const counter = outer();
counter(); // Output: 1
counter(); // Output: 2
counter(); // Output: 3

In this example, we have an outer function that defines a local variable count and an inner function called inner. The inner function increments the value of count and logs its value. The outer function returns the inner function.

When we call outer(), it returns the inner function, which we store in a variable called counter. Now, when we call counter(), it executes the inner function, which in turn increments the value of count and logs it. Notice how the value of count persists between calls to counter() even though the outer function has completed execution. This is the magic of closures!

Closure Analogy

To help understand closures further, let's use an analogy. Imagine you have a box with some items inside, and you also have a list of instructions on how to use those items. Now, you give the box and the list of instructions to a friend. Your friend can follow the instructions and use the items inside the box, even if you are not around.

In this analogy, the box represents the function's scope, the items inside the box are the variables, and the list of instructions is the closure. Your friend can use the closure to access and manipulate the variables within the function's scope, even after the function has completed execution (i.e., after you have left).

Practical Use Cases

Closures have many practical applications in JavaScript programming. Some common use cases include:

1. Encapsulation and Private Variables

Closures can be used to create private variables that cannot be accessed directly from outside the function. This is useful for encapsulation and protecting the integrity of the data.

function createBankAccount(initialBalance) {
  let balance = initialBalance;

  function getBalance() {
    return balance;
  }

  function deposit(amount) {
    balance += amount;
  }

  function withdraw(amount) {
    if (balance - amount >= 0) {
      balance -= amount;
    } else {
      console.log("Insufficient funds.");
    }
  }

  return {
    getBalance: getBalance,
    deposit: deposit,
    withdraw: withdraw,
  };
}

const myAccount = createBankAccount(100);
myAccount.deposit(50);
myAccount.withdraw(25);
console.log(myAccount.getBalance()); // Output: 125
console.log(myAccount.balance); // Output: undefined

In this example, the balance variable is private, and can only be accessed and modified through the getBalance, deposit, and withdraw methods.

2. Function Factories

Closures can be used to create function factories, where a function generates other functions with specific behavior.

function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(3)); // Output: 8
console.log(add10(3)); // Output: 13

In this example, makeAdder is a function factory that creates and returns new functions with specific behavior (adding a specific value).

Conclusion

Closures are an essential concept in JavaScript, allowing us to create more dynamic, modular, and efficient code. By understanding the basics of functions, scope, and closures, you can unlock powerful techniques for managing state, encapsulating data, and creating flexible, reusable code.

Remember, a closure is simply a function that can access its own scope, as well as the scope of its parent (enclosing) function, even after the parent function has completed execution. With this understanding, you can now start incorporating closures into your own JavaScript projects and harness their full potential.