One day I will find some Closure

Zaki Mohammed Zaki Mohammed
October 16, 2021 | 10 min read | 208 Views

Closure is the most John Cena concept of JavaScript which is been there in front of your eyes, but of course, you can't see it. So let us put some light on Closures (like literally) in this article and understand what it has to offer.

Album Post:

For quick read. Checkout this amazing album!

One day I will find some Closure
One day I will find some Closure - Local, Global Scope & Closures

Everything the light touches is our kingdom, as per Mufasa in Lion King. Likewise, everything related to functions in JavaScript offers you Closure. So if you are a beginner, intermediate, or ninja JavaScript developer, chances are very high that you have been used closures. So before we begin with the code let us understand what Google has to so say about the word Closure:

Google Closure Meaning | CodeOmelet

The second one is more suitable in terms of JavaScript closures; a function tied to its surroundings to form a closure. A function in JavaScript always remembers its origin story, from where it's been come from; due to this, it remembers the local and global variables.

A closure is nothing but functions that are tied to its lexical environment. A lexical environment is where the code is actually written. With respect to code, we have functions that can have local and global variables, which it can have access to; so closure basically lets the function access things present in its lexical scope; this concept is applicable throughout the JavaScript universe.

So if closures are everywhere then let's start from everywhere in the following sequence:

  1. Local, Global Scope and Closures
  2. Inner Functions and Closures
  3. setTimeout and Closures
  4. Application of Closure - Encapsulation/Data Hiding

Local, Global Scope and Closures

Local Scope: Variables within a function have a function/local scope in which they are accessible. The function variables can no longer be accessible outside the function boundary. So the function remembers its own variable at the time of execution, cool. Check out the below code:

function foo() {
    let local = 10;

    console.log(local);
}

foo();

// 10

Global Scope: Variables outside a function have a global scope in which they are accessible. The global variables can also be accessible inside a function, this is due to closure, as function remembers about their surrounding (lexical environment). So the function remembers its own variable plus the variable within the global scope at the time of execution, cool. Check out the below code:

let global = 20;

function foo() {
    let local = 10;

    console.log(local);
    console.log(global);
}

foo();

// 10
// 20

So accessing a global variable from a function will also be considered as forming a closure. Fascinating, such a vital piece of information was right there in front of our eyes without even acknowledging.

Inner Functions and Closures

If a function is at root level scope then the closure for given function will be global scope. Similarly, if a function is an inner function then the closure to it will be the parent plus the global scope. Also, if a function has many outer/parent functions then all these parents will provide closure to the innermost function. Let us support this theory with some examples:

let value0 = 1;

function outer() {
    let value1 = 10;

    function inner() {
        let value2 = 100;
        console.log(value0, value1, value2);
    }

    inner();
}

outer();

// 1 10 100

Here, the inner() function is forming a closure with the outer function and the global scope. The inner() function will remember its own surrounding (lexical environment) in which it is present and hence will be able to recall when it is invoked. We are invoking the inner immediately within the outer() function and making a call to the outer() function at the root scope.

But all of these are common and not grabbing any attention since it's obvious to a function to remember about its surroundings at the time of the call. Now let us tweak a little and check how the inner() function behaves when it is not called within its parent, rather somewhere on the root level.

let value0 = 1;

function outer() {
    let value1 = 10;

    function inner() {
        let value2 = 100;
        console.log(value0, value1, value2);
    }

    return inner;
}

const innerRef = outer();

innerRef();

// 1 10 100

This will too results same; here, the outer() function is not immediately calling the inner() function, instead it is returning the reference of inner() function. Later on, we are calling the outer() function which will return the inner() function reference, and then we will invoke the inner() function outside the scope of its parent. Interestingly, the inner() function doesn't show any signs of Alzheimer's disease and remembers its lexical environment even outside its parent's boundary. Very touchy!

You can even check this out by putting debugger point in either VSCode or Chrome (or any browser's) Source tab on the inner function's console log line. The debugger will show following closures in the debugging tab:

Closure Chrome VSCode | CodeOmelet

Checkout the below closures with inner() function references. These are the same as the above code with a slightly different approach to the implementation as mentioned in the comments. Internet is just a scary place with the same thing shown differently, don't worry we got this covered.

// creating inner function and
// passing the inline reference of inner function from outer function

let value0 = 1;

function outer() {
	let value1 = 10;
	return function () {
		let value2 = 100;
		console.log(value0, value1, value2);
	};
}

const innerRef = outer();

innerRef();

// 1 10 100
// creating outer function variable

let value0 = 1;

const outer = function () {
	let value1 = 10;
	return function () {
		let value2 = 100;
		console.log(value0, value1, value2);
	};
};

const innerRef = outer();

innerRef();

// 1 10 100
// creating inner ref directly by immediately invoking the outer function

let value0 = 1;

const innerRef = (function () {
	let value1 = 10;
	return function () {
		let value2 = 100;
		console.log(value0, value1, value2);
	};
})();

innerRef();

// 1 10 100
// creating inner ref directly by immediately invoking the outer function (arrow)

let value0 = 1;

const innerRef = (() => {
	let value1 = 10;
	return () => {
		let value2 = 100;
		console.log(value0, value1, value2);
	};
})();

innerRef();

// 1 10 100

setTimeout and Closures

Life is hunky-dory when everything is synchronous, but not until the arrival of async. The setTimeout() function is such an example of something which is async in nature. It is important to explore the behavior of closures in such unfaithful environments (async world). Let us first recap the setTimeout() function and the unfaithful environment which I am talking about:

// setTimeout execution sequence

console.log('before setTimeout');

setTimeout(function () {
    console.log('within setTimeout');
}, 0);

console.log('after setTimeout');

// before setTimeout
// after setTimeout 
// within setTimeout

JavaScript handles the async operations on a different timeline altogether, even if you run setTimeout() for 0 milliseconds. The callback of setTimeout will execute after completing all the sync operations. An ideal scenario for us to explore about closures.

If you have watched Inception movie then closures remind you of that; in which the lead had many levels of dreams and it was important for the character to disguise between these levels. The same happens for functions with closures. Whether sync or async a function remembers its lexical environment, thanks to closures. Let us test setTimeout with some examples:

// setTimeout can remember variables from its lexical scope 
// even they execute on a different timeline

let global = 1;

setTimeout(function () {
    let local = 10;
    console.log(global, local);
}, 1000);

// after 1000 milliseconds
// 1 10

Yes ofcourse, pretty straight forward code which runs after 1 seconds. The beauty of closure is that even after 1000 milliseconds the setTimeout callback function still remembers its lexical scope and recognize the global variable.

Adding one more level to this and let's check how setTimeout() behaves inside a function and not at the root level.

let value0 = 1;

function outer() {
    let value1 = 10;

    setTimeout(function () {
        let value2 = 100;
        console.log(value0, value1, value2);
    }, 1000);
}

outer();

// after 1000 milliseconds
// 1 10 100

Here, we have added one outer() function which holds the setTimeout(). So the outer() function is at level 1 and the callback function of setTimeout() is at level 2 and closures still proven to work.

For experiment, I have added more levels and tested the setTimeout() function closure to feel the Inception effect and man it works without a harm and for reference I have added some of the variations like with anonymous and arrow functions.

let value0 = 1;

function level1() {
    let value1 = 10;
    function level2() {
        let value2 = 100;
        function level3() {
            let value3 = 1000;
            function level4() {
                let value4 = 10000;
            
                setTimeout(function () {
                    console.log(value0, value1, value2, value3, value4);
                }, 1000);
            }
            level4();
        }
        level3();
    }
    level2();
}

level1();

// after 1000 milliseconds
// 1 10 100 1000 10000
let value0 = 1;

function level1() {
    let value1 = 10;
    return function () {
        let value2 = 100;
        return function () {
            let value3 = 1000;
            return function () {
                let value4 = 10000;
            
                setTimeout(function () {
                    let value5 = 100000;
                    console.log(value0, value1, value2, value3, value4, value5);
                }, 1000);
            }
        }
    }
}

const level2 = level1();
const level3 = level2();
const level4 = level3();

level4();

// level1()()()();

// after 1000 milliseconds
// 1 10 100 1000 10000
let value0 = 1;

const level1 = () => {
    let value1 = 10;
    return () => {
        let value2 = 100;
        return () => {
            let value3 = 1000;
            return () => {
                let value4 = 10000;
            
                setTimeout(() => {
                    let value5 = 100000;
                    console.log(value0, value1, value2, value3, value4, value5);
                }, 1000);
            }
        }
    }
}
const level2 = level1();
const level3 = level2();
const level4 = level3();

level4();

// level1()()()();

// after 1000 milliseconds
// 1 10 100 1000 10000

Dont get scared with these many brackets (level1()()()()), its just calling functions to get you finally out of the inner most function.

Application of Closure - Encapsulation/Data Hiding

The most common and primary usage of a closure concept is to have an encapsulation feature in place, where we can have kinda private variables within our closure functions. The default ES6 syntax for classes fails to have this feature in place, so no private variables for JavaScript right now (cries in a corner); but till then you can achieve it using closure functions. We will try to implement the encapsulation feature using dummy Counter and Account functions.

Counter Closure

For demonstrating data hiding we are creating a Counter closure function which maintain a counter variable for own and provide you an increment function to increment its value while not exposing the count variable itself.

// creating inner increment function and 
// passing the reference of inner increment function 
// from counter function

function counter() {
    let count = 0;
    return function () {
        return ++count;
    };
}

const counter1 = counter();

console.log(counter1())
console.log(counter1())
console.log(counter1())
console.log(counter1())

// 1
// 2
// 3
// 4

Here, the count variable is in the local scope of the counter() function but for the inner function, it's a closure and can access the parent's count variable. We are creating the counter1 function reference to the returning inner function which can access the local count variable without letting the outside world have it; cool.

Now let us check out the variants to the above counter function which we can possibly have so that we won't be left out with anything.

// creating inner increment & decrement function and 
// passing object from counter function

function counter() {
    let count = 0;

    function increment() {
        return ++count;
    }
    function decrement() {
        return --count;
    }
    function value() {
        return count;
    }

    return ({ increment, decrement, value });
}

const counter1 = counter();

console.log(counter1.value())
console.log(counter1.increment())
console.log(counter1.increment())
console.log(counter1.increment())

console.log(counter1.decrement())
// 0
// 1
// 2
// 3
// 2
// creating inner increment & decrement function and 
// passing object from counter function using arrow function

const counter = () => {
    let count = 0;
    return ({
        increment: () => ++count,
        decrement: () => --count,
        value: () => count
    });
}

const counter1 = counter();

console.log(counter1.value())
console.log(counter1.increment())
console.log(counter1.increment())
console.log(counter1.increment())

console.log(counter1.decrement())
// 0
// 1
// 2
// 3
// 2
// creating inner increment & decrement function 
// for constructor Counter function

function Counter() {
    let count = 0;

    this.increment = () => ++count;
    this.decrement = () => --count;
    this.value = () => count;
}

const counter1 = new Counter();

console.log(counter1.value())
console.log(counter1.increment())
console.log(counter1.increment())
console.log(counter1.increment())

console.log(counter1.decrement())
// 0
// 1
// 2
// 3
// 2
// creating inner increment & decrement function 
// for constructor Counter function with ES6 signature

class Counter {
    constructor() {
        let count = 0;

        this.increment = () => ++count;
        this.decrement = () => --count;
        this.value = () => count;
    }
}

const counter1 = new Counter();

console.log(counter1.value())
console.log(counter1.increment())
console.log(counter1.increment())
console.log(counter1.increment())

console.log(counter1.decrement())
// 0
// 1
// 2
// 3
// 2

Counter 4 is just a syntacically weirdo, as it is just a trick to use local variable of constructor function as private and initializing the class member functions within constructor.

Account Closure

Simply one more example of closure to have some more understanding how to use it in some real world scenario as created with Account closure function given below:

// basic encapsulation using closure

function account(number, type, baseAmount = 0) {
    let _number = number;
    let _type = type;
    let _balance = baseAmount;
    return {
        getNumber: () => _number,
        getType: () => _type,
        getBalance: () => _balance,
        withdraw: (amount) => amount > _balance ? 'Insufficient Balance' : (_balance = _balance - amount),
        deposit: (amount) => _balance = _balance + amount
    };
}

const account1 = account(2001, 'Savings', 1000);

console.log('Number:', account1.getNumber());
console.log('Type:', account1.getType());
console.log('Balance:', account1.getBalance());

console.log('Balance:', account1.withdraw(200));
console.log('Balance:', account1.deposit(300));
console.log('Balance:', account1.deposit(300));
console.log('Balance:', account1.withdraw(30000));
console.log();

const account2 = account(4001, 'Current', 500);

console.log('Number:', account2.getNumber());
console.log('Type:', account2.getType());
console.log('Balance:', account2.getBalance());

console.log('Balance:', account2.withdraw(200));
console.log('Balance:', account2.deposit(1000));

// Number: 2001
// Type: Savings
// Balance: 1000
// Balance: 800
// Balance: 1100
// Balance: 1400
// Balance: Insufficient Balance

// Number: 4001
// Type: Current
// Balance: 500
// Balance: 300
// Balance: 1300

We are creating 2 accounts objects using which we can do operations but without any direct access to the private members which are distinguished by underscore (e.g. _number, _type and _balance).

We can clearly understand that closure is basically Thanos (inevitable). Dread it. Run from it. Closures still arrive. So it is better to embrace it rather than avoid and scratching hairs later on. There are a lot more closures and very difficult to cover in a small read.


Zaki Mohammed
Zaki Mohammed
Learner, developer, coder and an exceptional omelet lover. Knows how to flip arrays or omelet or arrays of omelet.