Back to blog

Understanding Closure in JavaScript – How and why ?

Closure is one of JavaScript’s most powerful features, yet sometimes even veteran programmers struggle to understand it. What is Javascript closure? Why is it important? When does it happen, and how can it be leveraged to make code clean, elegant, and reliable? Despite the mystique that surrounds the term, closure is actually a simple concept at its core.

Closure simply means that a function always maintains access to every variable used within it, no matter how, where, or when it is executed. Even variables that would otherwise fall out of scope and be destroyed by the garbage collector are kept around as long as a function referencing them exists. Take a look at this example:

const MakeGreeting = function (name, city) {
let greeting;
if (name && title) {
greeting = `Hello, ${name} from ${city}!`;
} else if (name) {
greeting = `Good morning, ${name}!`;
} else if (title) {
greeting = `Hi! How's the weather in ${city}?`;
} else {
greeting = "Good to meet you! How are you today?";
}
return function() {
alert(greeting);
}
}

 

MakeGreeting takes in a person’s name and city, constructs an appropriate greeting, then gives back a function which, when invoked, will display the greeting in an alert. We can greet someone like this:

const greetMelissa = MakeGreeting("Melissa", "New York");
greetMelissa() // "Hello, Melissa from New York!"

The function greetMelissa() displays the text stored in the variable greeting, even though it’s being called from a context where the variable greeting wouldn’t ordinarily be available. greetMelissa() has closure over greeting! It can access the text it needs to display, even if greetMelissa is passed to another function to use later on instead of invoked immediately.

If programmers aren’t aware of closure and how it works, it can cause bugs. Consider another example:

for(var i = 0; i < 5; i++) {
setTimeout(1000, function(i) { console.log(i); });
}

What is displayed in the console? At first glance, it seems the console should print, after one second, “0 1 2 3 4”. But “5 5 5 5 5” is actually printed! Why? The anonymous callback function passed into setTimeout has closure over i. The loop finishes well before the timeout expires. When the loop is finished, the final value of i is 5. Then, when the timeout expires, each callback function is called, sees it should log the value of i, then goes back to the original function and asks for the value of i – which, by this time, isn’t what it was when the function was created.

How can the loop be corrected to display “0 1 2 3 4”? There are two common ways.

The call to setTimeout could be encased within an IIFE – an immediately invoked function expression – to create a new closure with the proper value to log:

for(var i = 0; i < 5; i++) {
(function(num) {
setTimeout(1000, function(num) { console.log(num); }
})(i);
}

When the IIFE is called, it creates its own closure, which keeps num (containing the correct value of to log) available until the callback is called. The IIFE is invoked five times, which creates five closures with five different values of num.

Alternatively, ECMAScript2015 (often called ES6) introduced the “let” and “const” keywords to declare variables. Unlike “var”, these two new keywords declare variables with block scope, rather than function scope. We can leverage this feature to fix the loop by changing how i is declared:

for(let i = 0; i < 5; i++) {
setTimeout(1000, function(i) { console.log(i); }
}

Since i now has block scope, a new copy of it is created for each iteration of the loop. Each callback function has access to i’s value at the time setTimeout was called, rather than its value at the end of the loop. The loop behaves as expected, logging “0 1 2 3 4”.

JavaScript Closures 101: What is a closure?

Every JavaScript programmer should understand closure. Fortunately, it’s a simple concept! Whenever a function is called, it looks up every variable used inside of it and uses the value it finds – even if that value comes from outside it. This makes it easy to write functions that incorporate variables from their surrounding environment, without worrying about whether or not they will be in scope at the time the function is called, as is necessary in many other languages.