The concept of closure is important when it comes to understanding how languages such as Javascript work - and how scopes, lifetimes and references come together and can be combined to provide useful features to programmers.
Variables and their scopes
1 - Local and global scope
Every time you use a variable inside a function, this variable needs to be declared somewhere - and this place can be the local scope (inside of the function) or the global scope.
Variables declared in the local scope are only accessible inside of their parent function - which means you cannot reference them outside of the function:
function() {
let a = 1;
console.log(a); // works
}
console.log(a); // fails
In the example above, written in JS, we get an Uncaught ReferenceError: a is not defined
.
However, by declaring a
as a global variable, we can reference it anywhere in our code:
var a = 1;
function() {
console.log(a); // works
}
console.log(a); // works
2 - Lifetime of entities
The behavior above happens because entities (variables, constants, etc) have different lifetimes across a program depending on their scope. The definition of the "lifetime" of an entity can be thought as the period from its creation (allocating memory for it) to its destruction (deallocating the memory used for this variable).
A global variable has the same lifetime of the entire program. Local variables, however, have specific lifetimes, so they may not be "alive" during the entire program’s lifecycle.
Hence, the underlying programming language must provide a way to deal which such cases, such as manual memory management or some kind of garbage collector. Otherwise, one could stumble upon a dangling reference when not careful enough, since deallocated variables cannot be referenced.
This dynamic is important to understand because we'll eventually get into a tricky question:
What would happen if we wanted to reference a variable outside of its original scope?
3 - Nested functions
There is a special use case of functions in which we face the question above.
Let's say we wanted to create a function inside of another function. Let's call them outer
and inner
.
const outer = function() {
const inner = function() {
console.log("hello");
}
}
Everything declared inside of inner
has now access to the scope of outer
.
It means that we can declare variables inside of outer
and then use them inside of inner
, as it follows:
const outer = function() {
const a = 1;
const inner = function() {
console.log(a);
}
return inner;
}
var fnc = outer(); // execute outer to get inner
fnc();
We can then return inner
from outer
. This part is important because it permits inner
to leave the scope of outer
.
After that, we assign the return of outer to a variable fnc
, which we can execute to get inner
.
Therefore, we now have all variables of inner
, declared in outer
, closed under this function.
What we'll seeing here is basically the implementation of closures in Javascript: a function that has access to the additional data from its surrounding scope.
Therefore, we can define a closure as:
1 - A function and
2 - A reference to that function's outer scope
In the example above, our function is inner
and the outer scope is the scope of outer
, which has the variable a
. Without keeping a reference to this surrounding scope, we would not be able to retrieve the value of a
. Once we have a reference to this surrounding scope, we have a closure.
Back to scope and lifetimes: what would happen if we didn't have closures?
There are languages that don't implement closures.
C is a good example: in C, you won't have closures as well as native support for nested functions.
Using the simplified definition we've seen before, we can say that what's needed to implement closures is a record of the function along with its environment.
To do this in C, there would be necessary to use structs, function pointers and a lot of fuzzy code. Furthermore, it would be necessary to have support for nested functions, which C doesn't have by default. Since closures are only meaningful when a function needs to be executed outside of the scope in which it was originally declared, having nested functions is essential for this concept to work.
A lot of work, huh?
How Javascript solves it?
We already learned that JS implements closures and what they are. But how does it work under the hood?
Every function in JavaScript maintains a reference to its outer environment. This reference is used to determine the execution context of the function when it is invoked, and therefore enables the function to "see" variables declared outside of it.
It creates a kind of state particular to the functions declared in the same lexical environment.
💡 The lexical environment consists of any local variables in the function’s scope when the function is created.
In addition, if we have functions that call other functions that in turn call even more functions, JS creates a chain of references to the outer lexical environments - which is called the scope chain. Therefore, closures in Javascript can become quite useful - and we are going to see a few uses for them now.
Why are closures useful?
If you have ever programmed using an object-oriented language, you will probably know the concept of classes and states.
Once you use a closure, you are creating the equivalent of a private state associated with a function.
function foo() {
const secret = Math.trunc(Math.random() * 100)
return function inner() {
console.log(`The secret number is ${secret}.`)
}
}
const f = foo() //
f()
In the example above, secret
is not directly accessible from outside foo
. foo
then creates a kind of encapsulation similar to what we see in OOP, which is useful in several cases. Furthermore, JavaScript did not have a class syntax until 2015 - making the use of closures an alternative.
Finally, there are several other use cases for closures - for example in functional programming, event-oriented programming and so on.
💡 Further reading: Nested functions, closures and first-class functions
Top comments (12)
Something to add: you can create new scopes anywhere you want, provided the compiler doesn't think you're trying to initialize an object. A function body isn't necessary.
Though, doing that looks ugly and probably isn't ideal. An example of a valid use case is switch statements, it allows you to isolate each case into its own local scope.
You are right, this is also something interesting about scopes. Thanks for pointing out!
In ReactJS, my first encounter with closures was in form of arrow functions:
I was so confused at first, then i was like: wait a sec, it is just something that returns something that in return returns something. Aha! Know what i am sayin'?
Edit: For the sake of correctness, I think this statement is somewhat wrong:
Currying is the broader term, and closure is just a practice of it in programming.
Thanks for pointing out! I've updated the post 😆
Your posts are getting better and better every time, really good explanation and examples
Tnx, I'm really glad to help 😊
nice post, congratz
Thanks 😊
Hello wrongbyte,
Thank you for your article.
I read something about closures years ago.
Reading your article helped me refresh some of my knowledge about closures.
I'm glad I could help! 😄
Nice post. Closures are one of the most useful concepts to understand in languages that have them.
For sure! And they are very interesting