Understanding JavaScript closures

Carlos Morales

Carlos Morales • 2021-09-07

For many years, understanding JavaScript (JS) closures has been the key interview question, this proved a good understanding of JavaScript. Interestingly, most of the descriptions you find in Internet about this topic are hard to understand and leave you with more questions than answers. I believe I have a simpler way to explain JS Closures.

In my company, as my colleagues consider me the subject-matter expert on JavaScript (imposter syndrome? sure!), they asked me multiple times the same question ”how does a JS Closure work?”. So I developed an answer to easily understand them.

There are two pieces of JavaScript that in isolation are easy to understand:

When these two are merged, the Closures become also easy to understand.

Functions as first-class citizenship

Functions are first-class citizenship in JavaScript, this means functions can do what any other variable can do. All these are valid:

Let’s see this in action:

function getOperation(name) {
  const availableOperations = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b,
    multiply: (a, b) => a * b,
    division: (a, b) => a / b,
  };
  return availableOperations[name];
}

let operation = getOperation('add');
console.log(operation(5, 6)); // 11

operation = getOperation('multiply');
console.log(operation(5, 6)); // 30

In the previous simple example, there are four functions (the available operations) stored in an object, one of them is returned, then stored inside the operation variable and finally it is executed. You get the idea.

JavaScript lexical-scope

Lexical scope is the scope that is defined at lexing time. I know, this definition may be confusing. Bear with me. Let’s break it down:

In other words, lexical scope is based on where variables and blocks of scope are written down in the code. In JavaScipt, you can access all variables that were defined in the same function and in higher nested functions when you wrote that code.

An example will clarify this instantaneously:

// sum all the numbers in the passed array
function aggregate(numbers) {
  let result = 0;

  numbers.forEach(function sum(num) {
    result += num;
  });

  return result;
}

In the previous example nobody is surprised by the sum function accessing to result variable, although it was not declared inside or passed as an argument. How is this possible? there are two nested scopes in this code example. Each function has like a “bubble” (that acts as a container or bucket), the outer bubble (the aggregate function) and the inner bubble (the sum function). The inner bubble has access to the outer bubble.

And that is how sum function can access to numbers variable although it was not originally defined inside that block.

How does the inner function access to all those variables? with the closure! In other words, a closure gives you access to an outer function’s scope from an inner function.

Closures

In JavaScript, closures are created every time a function is created, at function creation time.

Each time a function is created, the JavaScript engine creates a reference to all the variables that are accessible in the current execution context (defined by the lexical scope). As this function can be returned (because it is a first-class citizen), the closure remains active until that variable is released by the garbage collector.

This technique is quite useful when we need to hide the access to some variables.

function counterSetup() {
  // 'value' can only be used inside 'counterSetup' function
  let value = 0;
  return {
    increment: () => ++value,
    decrement: () => --value,
  };
}

const counter = counterSetup();

console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.increment()); // 3
console.log(counter.decrement()); // 2

console.log(counter.value); // undefined

In the previous example, the variable value is accessible from the increment and decrement closures, it is not accessible from outside. And although the variable is hidden, it exists and can keep a state.

Final remarks

There are three important and recurrent topics to consider when Closures are discussed:

Function versus block scope

Before ES6, JavaScript was only a function-based scope programming language. That is, each function you declare creates a bubble for itself and no other structures create their own scope bubble. In other words, variables declared with var are function-scope. Which means the function creates the bubble and anybody can reference those variables from inside.

ES6 variables redefined those terms as the variables created with const and let are block-scoped: any block creates a scope, this is for or if statements create their own scopes. This approach is more common in other languages (new developers will be more familiar) and it declares variables as close and local as possible to where they will be used.

Blocks only scope let and const declarations, but not var declarations.

At the same time, because only functions can be returned, only the variables accessible at the function level are accessible in Closures.

Memory leaks

Every time I explained Closures to smart engineers they immediately ask me ”but closures create memory leaks!”. Yes, that is true, as soon as you return a function and that is accessible, the whole scope will not be destroyed by the garbage collector and that is actually a memory leak only if you do not plan to use it anymore.

Nevertheless, please, do not panic. This was an issue years ago, when browsers were not so optimized and devices had a very small memory stack. These days modules keep the scopes way smaller than global scope. The last years I have not encountered this issue anymore.

As a rule of thumb, be careful when assigning functions to the DOM, that reference will prevent the browser to reclaim the memory used by the associated Closure.

Cheating lexical scope with eval

The eval(...) function in JavaScript takes a string as an argument and treats the contents of the string as if it had actually been authored code. To make this happen, JavaScript engine will generate the code at runtime as it was defined originally at the author time.

Yes, you can modify the content of the Closure at runtime with the eval(...) function, but that is a bad idea.

Let’s see how this trick can cheat the lexical scope:

function parse(operationString, a, b) {
  let result;
  eval(operationString);
  return result;
}

const multiplication = 'result = a * b;';

const returned = parse(multiplication, 3, 6);

console.log(returned); // 18

The previous code modified the result variable. It is so shaky and error-prone it hurts my eyes. If that does not convince you, eval(...) functions are one of the weakest points for your web security. Please, never-ever-ever use the eval function and turn on the 'unsafe-eval' Content Security Policy (CSP) directive to make sure nobody in your organization does.

Conclusions

A closure gives you access to an outer function’s scope from an inner function.

Because functions are treated as variables, the closure of each function remains active until the variables are released.

If you want to know more details, please read the book You Don’t Know JS Yet: Scope & Closures by Kyle Simpson, there is no better place for learning about Closures.