Everything about Closures
In this post, we will build upon our previous post in Capitole’s blog about higher order functions by exploring the concept of closures in programming. We will delve into what closures are, examine their representation in code, and discover the practical problems they can help us solve in our day-to-day development processes.
A closure refers to a function’s capacity to remember the context in which it was created, even when executed in a different context. This means that any identifier declared in the function’s creation context, including variables, constants, or other functions, remains accessible to the function implicitly, regardless of where the function is ultimately executed. To illustrate this concept, let’s explore a straightforward example of a closure.
constant, allowing it to produce the expected result, which, in this case, is 2. This behavior occurs because the function effectively “closes over” the variables existing at the time of its creation, retaining references to these values.
Now, let’s explore the following example to see how functions can retain their context even when executed outside of it.
This is a pattern we’ve already encountered in the blog post about higher-order functions. In this scenario, a function can both receive and return another function. In the example, the adder() function is created inside the buildAdder() function and then returned. Since it originates within the buildAdder() context, it retains access to this context and can use it even outside of it. This explains why twoAdder(), which is essentially the adder() function exposed outside of the buildAdder() context, can still remember and utilize the factor parameter when executed.
Uses cases
Encapsulation and data privacy
Closures, by their very nature, are instrumental in creating data encapsulation, allowing for the creation of private variables that can be securely accessed and modified while preserving data invariants.
In the example above, the createEmployee() function returns an object representing a company’s employee. This object utilizes closures to provide controlled access and modification of private variables created within the createEmployee() function, such as ‘name,’ ‘age,’ and ‘salary.’ It’s important to note that outside of this function, these variables are not publicly accessible. Instead, we can only interact with them using the interface provided by the createEmployee() returned object, which manages updates and ensures data invariants.
Higher order functions
As previously discussed in the post, closures are frequently employed in higher-order functions to pass a function while preserving the surrounding context.
In this case, even though done() is executed within the doSomethingAsync() function, it retains access to its original creation context, allowing it to continue accessing the fileCreationTime value.
The same principle can be employed to implement partial application. This technique allows us to incrementally provide parameters to a function, resulting in the creation of another function whose parameters are the remaining ones from the original function.
While the examples provided here are written in JavaScript, it’s important to note that closures are not exclusive to JavaScript. They are a fundamental concept in any programming language that treats functions as first-class citizens, including Python, Golang, Ruby, Java, C#, and many others.
For example this is an example written in Golang
Despite being written in different programming languages, closures look similar and are a technique that can be used across them.