Understanding Closures in JavaScript
Tags: webdev, javascript, beginners, programming
Published: 2024-06-06 06:21:48

## Table of Contents

[What's a closure](#chapter-1)

[Why are they needed](#chapter-2)

[Encapsulation & State Management](#chapter-3)

[Higher Order Functions](#chapter-4)

[Function Currying & Composition](#chapter-5)

[How Javascript libraries leverage closures](#chapter-6)

---

In the vast world of JavaScript, closures stand out as one of the most powerful and intriguing concepts. Whether you're a seasoned developer or just starting your coding journey, understanding closures is essential for mastering JavaScript.

In this blog post, we'll demystify closures, exploring their fundamental principles, practical applications, and why they are indispensable in modern JavaScript development.

By the end, you'll have a clear understanding of how closures work and how to leverage them to enhance your coding skills.

---

## What's a closure ? <a name="chapter-1"></a>

A closure is a fundamental concept in JavaScript (and many other programming languages) that allows a function to retain access to its lexical scope, even after the function that created the scope has finished executing.

To be honest, the definition does not give an idea about the power of closures🙈.

So what if a function can retain its lexical scope ? What's the big deal ?

Believe Me ❤️ , closures are an infinity stone in the gauntlet of functional programming 🔮

---

## Why are they needed ? <a name="chapter-2"></a>

In modern day code development, Functional Programming is highly leveraged because it has certain advantages over OOPS in certain areas.

With this change in approach, we still needed to support basic features of OOPS & clean coding.

Functional Programming has it's unique ways of implementing these features

1. Modular & Reusable Code.
2. Encapsulation & State Management.

This is exactly where closure is needed.

**💡Functional Programming is easier to understand with examples. So i'll provide lots of them.**

{% collapsible What is functional programming? %} 

- For simplicity, let's say functional programming is all about thinking in terms of functions & smartly leveraging them in our code.
- Functions are treated as first class citizens. They can be declared as a variable, can be passed as arguments, can be returned from another function & much more. 

{% endcollapsible %}

---

## Encapsulation & State Management <a name="chapter-3"></a>

Functional Programming has no concept of access specifiers - `public, private & protected.`

So how do we achieve encapsulation?

`Closure` is one of the ways you can achieve encapsulation.

**Use-case**

- We have a variable count & we want to allow limited operations on it - `increment, decrement, reset & get.`
- We want to prevent count from external access i.e. keep it private.

First let's see OOPS way of achieving encapsulation.

### Encapsulation using OOPS


```javascript
class Count {
    private _count = 0;
    function increment(){
        this._count++;
    }
    function decrement(){
        this._count--;
    }
    function reset(){
        this._count = 0;
    }
    function getCount(){
        return this._count;
    }
}

const count = new Count();
```

### Encapsulation using Functional Programming


```javascript
function count() {
    let count = 0;
    return {
        increment: function(){
            count++;
        },
        decrement: function(){
            count--;
        },
        reset: function(){
            count = 0;
        },
        getCount: function(){
            return count;
        }
    };     
};

const fpCount = count();
```

Now does this statement ring a bell ??? Let's see...

`Closure allows a function to retain access to its lexical scope, even after the function that created the scope has finished executing`.

When we called `count()` it executed & returned us methods to play around with count variable.

But even after its execution, all handler methods remember value of count because they have access to their lexical environment.

Also using `closures` the handler functions manage the state of count variable.

This way we've achieved encapsulation & state management of count variable 😉🎉

---

## Higher Order Functions <a name="chapter-4"></a>

> A higher-order function is a function that either Takes one or more functions as arguments or Returns a function as its result.

The purpose of Higher Order Functions is to make the code modular & reusable.

Use-case

- We want to loop over an array

```javascript
// Classical way to loop over an array
const numbers = [1,2,3,4,5,6];

for(let i = 0; i < numbers.length; i++){
    console.log('number ', numbers[i], ' is at index ', i);
}

// Same using functional programming

function printElementAndIndex(element, index) {
    console.log('number ', element, ' is at index ', index);
}

// For any beginners reading this, 
// forEach is a higher order function provided by javascript
numbers.forEach(printElementAndIndex);
```

You see `printElementAndIndex` was called for every element in numbers array.

It remembered the values of element & index passed to it at that iteration.

- Attaching event handler

```javascript
// Assuming we're attaching a click listener 
// on button with id 'my-button'

const button = document.getElementById('#my-button');

const handleButtonClick = (event) => {
 // handle click event here
}

// 'addEventListener' is the higher order function here
// because it accepts function as an argument
button.addEventListener('click',handleButtonClick);

```
`addEventListener` finishes its execution the moment UI is rendered.

`handleButtonClick` is a callback which executes later but it still gets access to `Event` object because of closure.

---

## Function Currying & Composition <a name="chapter-5"></a>

> Function Currying is a technique using which a function with multiple arguments is transformed into a series of functions each taking one argument.

> Function Composition is a technique using which we can combine two or more functions to create custom functions.

These techniques also help us write `Modular & Reusable code` by leveraging `Pure Functions` which is one of the fundamental advantages of using Functional Programming.

{% collapsible What are Pure Functions? %} 

Functions which return consistent output provided it receives consistent input. They cause no side-effects, do not modify any external state & make code more predictable.

{% endcollapsible %}

**Use-case**

- Let's say we have to write a function which calculates bill.

The conditions are,

1. We give 10% discount if amount is greater than 1000.

2. We levy 7% service charge on total amount.

This is how we could've written it without functional programming.

```javascript
function calculateBill(amount) {
    let totalAmount = amount;
    if(totalAmount > 1000){
        totalAmount = totalAmount - ( totalAmount * 10 / 10 );
    }
    return totalAmount + ( totalAmount * 7 / 100 );
}
```

Although this is a working function, it has some pitfalls.

1. Values of service charge & discount are hardcoded. In future if we have multiple discount offers depending on amount it's hard to adapt. Either too many if & else blocks or code duplication if separate function for each condition is created.

2. It's not a pure function.

Now let's write the same using `Currying` & `Composition`.

```javascript
// Function Currying
// Notice how we split into two funtions
// Each handles one argument & makes function modular & reusable
function calculateDiscount(discountPercentage, eligibleAmount){
    return function(amount){
        if(amount > eligibleAmount) {
            return amount - ( amount * discountPercentage / 100 );
        }
        return amount;  
    }
}

function addServiceCharge(taxPercentage){
    return function(amount){
      return amount + ( amount * taxPercentage / 100 );
    }
}

// Later on if we change discount & tax percentages, 
// we can quickly adapt.
// Notice that these are pure functions.
const discountByTen = calculateDiscount(10, 1000);
const levyServiceChargeOf7 = addServiceCharge(7);

// Function Composition
// Notice how we combined existing functions to create new function
const composeBillCalculator = (levyServiceCharge, applyDiscount) 
=> amount => levyServiceCharge(applyDiscount(amount));

// Currently we have, 
// 1] 10% discount
// 2] 7% service charge
const calculateBill = 
composeBillCalculator(levyServiceChargeOf7, discountByTen);

export { calculateBill };
```

---

## How Javascript libraries leverage closures <a name="chapter-6"></a>

### React

[React](https://react.dev/), a popular frontend javascript library leverages closures to manage state of the component.

Also it has a concept of [Hooks](https://react.dev/reference/react/hooks) which entirely based on closures.

```javascript
import React, { useState, useEffect } from 'react';

function Timer() {

    // useState leverages closure internally.
    // setSeconds is a setter to update state.
    // seconds acts like a getter.

    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds(prevSeconds => prevSeconds + 1);
        }, 1000);

        return () => clearInterval(interval); // Cleanup
    }, []); // Empty dependency array to run once

    return <div>{seconds} seconds have passed.</div>;
}
```

### Nodejs

[Nodejs](https://nodejs.org/en), a popular backend javascript runtime, leverages closures when managing asynchronous execution via callbacks.

**Use-case**

- File read operation

```javascript

const fs = require('fs');

function readFile(filePath) {
    fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) {
            console.error('Error reading file:', err);
            return;
        }
        console.log('File contents:', data);
    });
}

readFile('example.txt');
```
The `callback` function inside `fs.readFile` forms a closure that retains access to `filePath` and any other outer variables.

This closure ensures the callback can use these variables even after the asynchronous file read operation completes.

[ExpressJs](https://expressjs.com/) is a popular framework for Nodejs. 
It has a concept called [middlewares](https://expressjs.com/en/guide/using-middleware.html) which uses closures.

---

I hope you understood how cool the closures are 😍

Do let me know in comments if you know any other usage of closures.

Go ahead leverage them in your code with confidence 😊

Additionally, closures are a popular topic in technical interviews. With the knowledge and examples provided here, you should be well-equipped to explain and demonstrate closures with confidence during your next interview.

Thank you for reading, and happy coding!