JavaScript Primer: Comprehending Closures

Intro to closures, data protection, and partial function application

Zach Morgan
8 min readNov 15, 2022
Photo by iMattSmart on Unsplash

Closures are a core concept in programming, and they appear in many different languages. They have a number of use-cases, including adding flexibility to code and controlling access to sensitive data. Understanding closures and being able to implement them in whatever language you are using is an important skill for a developer. In this article we will take a look at how closures work in JavaScript, and cover some common use-cases. Through this article you will see how closures are core concepts for implementing callback functions and data protection. We will also look at using closures for partial function application.

What is a closure?

Broadly defined, a closure is a piece of code that is encapsulated along with elements from its surrounding context. That code can then be passed around in the program, and executed somewhere else. When it is executed, the code in the closure can access data from its original environment, even if that data is inaccessible in the new execution context.

Exactly how closures are implemented differs from language to language. While Ruby uses blocks and a special object called a “proc” to generate closures, JavaScript builds the concept of a closure into the behavior of its functions. Specifically, a closure in JavaScript is a function definition combined with all of the in-scope variables that it uses. Let’s demonstrate this with a simple example:

On lines 1–3 we declare three variables — city, forecast, and news — and initialize them to string values. Because they are declared in the top-level scope, they are available to be referenced anywhere in the program; they have global scope. On lines 5–7 we declare a function reportWeather, which logs a string to the console. The expression passed to console.log() is a template literal which interpolates the values of city and forecast into the string. Where is the concept of a closure in this code?

When reportWeather is declared it recognizes two things: (1) it needs city and forecast in order to execute, and (2) both of those variables exist in the current scope. Based on these observations, reportWeather forms a closure that includes these two variables names. On line 9, reportWeather() is invoked. When line 6 executes, reportWeather looks in its local scope (inside the function body) to see if any variables named city and forecast are declared there. Since it does not find them, it then checks the variable names in its closure. city and forecast exist in the closure, so reportWeather can use those variable names to lookup the current value of those variables.

Notice that although the variable news is also in-scope when the function is defined, it is not included in reportWeather's closure. Functions only form closures with variables that they actually need in order to run.

Closures are formed lexically

Another characteristic of JavaScript closures is that they are formed according to the structure (or layout) of the program. The closure is not created at runtime. This trait is often referred to as a lexical trait. Check it out — pay special attention to the value of news and the output of reportNews():

The lexical nature of closures is the reason why reportNews() outputs "BREAKING! Blazers win!" instead of "BREAKING! Thorns win!". When the reportNews function is declared on lines 3–5, if forms a closure with the news variable declared on line 1. The closure references that variable in particular. That top-level news variable is shadowed inside the block on lines 7–11 by another variable with the same name. This locally-scoped news is the variable that is in-scope when reportNews() is invoked. However, since reportNews has a closure with the news variable from line 1, it looks up the value of that variable when it executes. This demonstrates that the closure was formed according to where reportNews was defined, not where it was eventually invoked.

Closures contain references

When a closure is formed, it does not capture the values of the variables which are part of it. The closure only contains references to the variables. This has important consequences. Although a closure is formed lexically, the value of each variable in the closure is retrieved at the time the function executes. This is part of what makes closures so powerful — the values of the variables in the closure can change during runtime:

When reportNews is defined on lines 3–5, it forms a closure with the news variable. The closure contains the identifier news, but not the value 'Blazers win'. On line 7, news is re-assigned to a new string 'Thorns win'. When reportNews() is called on line 9, it looks up the current value of news, which is 'Thorns win', and interpolates it into the template literal.

The fact that JavaScript closures have both lexical character and runtime character can lead to confusion. To keep everything straight, take note of which variables are in-scope where the function is defined. After that, pay attention to any changes that happen to those variables before the function is invoked.

Callback functions

Closures are a great solution to a variety of problems. The most common use-cases include callback functions, data encapsulation, and partial function application.

Callback functions are probably the most common places that a developer will encounter closures. A callback function forms a closure with variables in its surrounding context, and makes those variables available to a method that wouldn’t have access to them otherwise. Here’s a quick example:

On lines 1–2, we declare variables numbers and increment, and initialize them to an array and a number respectively. On line 4, a variable biggerNums is declared with an initializer. In the initializer, map() is called on the array referenced by numbers, and an anonymous arrow function is passed to map()as a callback function argument. Since increment is not in-scope where the map() method is defined (it is in-scope where map() is invoked, but not where it is implemented), map()does not naturally have access to increment. However, the callback function has increment as part of its closure, so it is able to look up the value of increment each time map() invokes the callback function. This allows map() to use increment’s value even though map() does not have access to it directly.

Securing data

Closures are a powerful tool for implementing data protection. Typically, a program that handles sensitive information should prevent it from being easily accessed or changed. Instead, there should only be a few well-defined pathways for reading or updating the data. Writing a function which returns a closure is one way build that functionality.

To illustrate this, let’s write a small program that handles a credit score. In general there two ways to check someone’s credit score. The first is a soft-inquiry, which consumer sites like Credit Karma tend to use. A soft inquiry can look up the credit score without affecting its value. When you go to apply for a loan however, the lender will perform a hard-inquiry, which can affect the credit score by a small amount. For our program, these are the only two ways to access the credit score. We don’t want anyone to have access to the data without going through one of the two channels we define.

Let’s define a function called createCheckers which generates a credit score and returns two functions that can access that value:

On lines 4–6 and 8–11 we declare two constants — softInquiry and hardInquiry — and initialize them to anonymous function expressions. Both of these functions form closures that include a reference to the creditScore variable declared on line 2. When createCheckers() is invoked on line 16, it returns these functions as a two element array, and we use array destructuring to assign each of them to a newly declared variable, softInquiry and hardInquiry. The functions represented by softInquiry and hardInquiry are now the only ways to access the value of creditScore — its scope is confined to the createCheckers function body.

softInquiry() is called on line 18, and since it has a closure with creditScore, it can look up its value and return it on line 5. Next, we invoke hardInquiry() on line 19. This function decrements creditScore by 5 on line 9 before returning its value on line 10. When we perform another soft inquiry on line 20, we see that the credit score was indeed harmed, and it remains 595 after the hard inquiry. Using closures, we were able create a system where a value can only be accessed in two ways: it could be either read without detriment, or it could be read at the cost of 5 points. No other manipulation of the credit score is possible.

Partial function application

In my opinion, this is one of the trickier ways that closures are used in JavaScript. We will look at one simple example, and leave a deeper dive for another day. Let’s say that we have a function that accomplishes some task. The only problem is that the function requires a certain minimum number of arguments to work properly, and the API we are using to call the function can’t pass enough arguments. We can use a closure to “apply” some of the arguments to our function before it is actually invoked in the program. Take a look:

On lines 1–3 we have a function joinWords which expects two string arguments and returns the result of concatenating them. This works great until we encounter a situation where we want the functionality of joinWords, but can only pass one argument. This takes place on lines 12 and 13 where we call a function called greet, which only takes one argument — a name — but needs to output a string that also has a greeting.

We accomplish this by defining a function createGreeter which takes the greeting as an argument, and returns an anonymous function which uses that greeting value to call joinWords. When that anonymous function is created, it forms a closure with the greeting variable, and “fixes” the reference to that variable to the joinWords argument list. When createGreeter returns on line 11, it returns a function definition that can take a single argument. When that function which is now assigned to greet is invoked, it takes a single argument, using it and the greeting variable from its closure to call joinWords with two arguments. We have successfully created a way to get the functionality of joinWords with a function call that has fewer arguments than joinWords requires. This is the essence of partial function application.

Hopefully by now the utility of closures is clear. They are a core component of JavaScript, and for good reason. In the course of using the language you are almost sure to use them, even if you don’t realize it at the time. Having the ability to recognize closures will make understanding and debugging code much more straightforward. Being able to implement them to solve a problem adds a indispensable tool to your tool belt. Practicing closures until they become intuitive is well worth the time and effort it requires.

This is last of three articles that I wrote concerning fundamental topics in JavaScript. Feel free to check out the first two JavaScript Primer articles in this series: variables and scope, and hoisting.

--

--