One day I will find some Closure

Shaikh Zaki Mohammed
14 min readJan 4, 2023

--

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.

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:

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. Higher Order Functions and Closures
  5. 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:

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.

Inner 1:

// 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

Inner 2: Outer function variable

// 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

Inner 3: IIFE — Immediately invoked function expression

// 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

Inner 4: IIFE with Arrow Function

// 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 setTimeoutJavaScript 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.

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 distinguish 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 of course, 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.

setTimeout 1: Calling inner functions within

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

setTimeout 2: Returning functions

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

setTimeout 2: Returning arrow functions

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.

Higher Order Functions and Closures

I don’t know who needs to hear this but there is absolutely no need to get high, to understand higher-order functions in JavaScript. A function is called a higher-order function if it can either accept a function as a parameter or return a function. The presence of closures can be detected from a good distance in higher-order functions.

We are considering both scenarios of a higher-order function:

  1. Pass function as parameter
  2. Return function from a function

Pass function as parameter

// passing function as parameter

const num1 = 6;
const num2 = 3;

const compute = operation => operation();

const add = () => num1 + num2;
const sub = () => num1 - num2;

console.log(compute(add));
console.log(compute(sub));

// 9
// 3

Here, we are passing a function as a parameter to the compute function. What we want to identify here is the presence of closure. Inside the add() and sub() functions, we are directly trying to use the global num1 and num2 variables. The beauty of closures is that it allows a function to remember its lexical environment even far away from home. In the above same happens to add() and sub() functions, even though they are not directly executing at the root level, instead, they invoked within the boundary of compute() function, they still remember the num1 and num2 variables.

This one is tricky to understand at first glance, but what we need to understand here is that the add() and sub() functions are called inside some other function’s scope but remember about its surrounding.

The debugger shows how closure is behaving with higher order function, when we pass function as parameter:

Higher order array functions are pure example of such cases; consider the below data structure:

const employees = [
{ id: 1, firstName: 'John', lastName: 'Doe', salary: 1200 },
{ id: 2, firstName: 'Allen', lastName: 'Green', salary: 2200 },
{ id: 3, firstName: 'Smith', lastName: 'Woods', salary: 5400 },
{ id: 4, firstName: 'Berry', lastName: 'Allen', salary: 4300 },
{ id: 5, firstName: 'Clark', lastName: 'Kent', salary: 3300 }
];

Exploring some queries with higher-order array functions:

// get employees having salary greater than 4k
const result1 = employees.filter(i => i.salary > 4000);
console.log(result1);

// get employee whose id is 3
const result2 = employees.find(i => i.id === 3);
console.log(result2);

// get employee index whose id is 4
const result3 = employees.findIndex(i => i.id === 4);
console.log(result3);

// get id and full name of employees
const result4 = employees.map(i => ({ id: i.id, fullName: `${i.firstName} ${i.lastName}` }));
console.log(result4);

/*
[
{ id: 3, firstName: 'Smith', lastName: 'Woods', salary: 5400 },
{ id: 4, firstName: 'Berry', lastName: 'Allen', salary: 4300 }
]
{ id: 3, firstName: 'Smith', lastName: 'Woods', salary: 5400 }
3
[
{ id: 1, fullName: 'John Doe' },
{ id: 2, fullName: 'Allen Green' },
{ id: 3, fullName: 'Smith Woods' },
{ id: 4, fullName: 'Berry Allen' },
{ id: 5, fullName: 'Clark Kent' }
]
*/

Return function from a function

Such cases are rare and mostly seen when you are implementing the concept of currying in JavaScript; by which your function needs to return a function and this chain goes on and on, and on.

// returning function as parameter (currying)

const splitter = by => value => value.split(by);

const dashSplitter = splitter('-');
const commaSplitter = splitter(',');

const result1 = dashSplitter('thunder-bolt-attack');
const result2 = commaSplitter('thunder,bolt,attack');

console.log(result1);
console.log(result2);

/*
[ 'thunder', 'bolt', 'attack' ]
[ 'thunder', 'bolt', 'attack' ]
*/

Here, we are trying to mimic the same case where we have created a splitter function that returns another function that accepts one argument named value (using which we split the string) and returns an array of split values. We have created 2 smart splitter functions using the main splitter function, one which splits the string based on the dash and the other one based on the comma.

Here also we can smell the essence of closures when the inner splitter function is called it accepts the “by” value from its outer parent function, something beyond the scope of inner splitter function; but all thanks to closure even not certain about when the smart splitter function gets executed it makes them remember about their parent function’s “by” value.

The debugger shows how closure is behaving with higher order function, when function is returned from another functions:

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.

Counter 1:

// 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

Counter 2: Using Arrow Functions

// 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

Counter 3: Using Constructor

// 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

Counter 4: Using Class

// 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.

Git Repository

Check out the git repository for this project or download the code.

Download Code

Git Repository

Summary

Man! this concept took a whole lot to understand at first glance. So simple yet so tricky to put in the head of mortals like us. Once understood close the laptop and head straight to the bed to keep it saved for a long duration (joke).

Jokes apart, it is very important to know this concept (A) if you don’t wanna let JavaScript creeps you out at any point in time (B) most common interview question by far, and (C)of course you deserve closure with JavaScript. The ninja technique to understand this concept better is to get your hand dirty and try all the possible outcomes with closures you can.

Hope this article helps.

Originally published at https://codeomelet.com.

--

--

Shaikh Zaki Mohammed
Shaikh Zaki Mohammed

Written by Shaikh Zaki Mohammed

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

No responses yet