Wednesday, February 7, 2018

The Blurry Line Between Closures and Not

What is a closure? It's a question that shows up all the time in blogs about interview questions. But in JavaScript, the line between what is and isn't a closure is a bit blurry.

Here are three questions to consider when deciding if a function is a closure:

  1. Is the function conceptually a closure? That is, does the function refer to non-local variables (also called free variables)?
  2. Under the hood, does the function use the implementation technique of a closure? That is, is the function really a record storing a plain function and an environment?
  3. And finally, does the function need to use the implementation technique of a closure? That is, could an interpreter quietly omit the record storing an environment and still get a correct result?

Let's start with something that is unambiguously a closure:

function f() {
    const name = "Mozilla";
    function displayName() {
        console.log(name);
    }
    return displayName;
}

f()();

Here we have an inner function that refers to a non-local variable defined in an outer function, and we execute the inner function after the outer function has finished and returned. The inner function is conceptually a closure since it refers to a non-local variable. It also uses the implementation technique of a closure, since it's really a function object that stores an environment. And finally, it needs to use the implementation technique of a closure, otherwise the non-local variable name would have been destroyed before the inner function was executed.

Next, let's look at an example where the third criteria from the list above doesn't apply.

function f() {
    const name = "Mozilla";
    function displayName() {
        console.log(name);
    }
    displayName();
};

f();

Like before, we have an inner function that refers to a non-local variable defined in an outer function, but this time we execute the inner function before the outer function has finished and returned. The inner function is still conceptually a closure since it refers to a non-local variable. It also still uses the implementation technique of a closure since it's really a function object that stores an environment. They key difference is this function doesn't need to use the implementation technique of a closure. The inner function is only ever executed when the outer function's stack frame and all its variables still exist. Which means the inner function could just be a plain function -- no stored environment -- and it would still be able to safely access the outer function's variables. In fact, to prove this is true, we can write this same code in C and it would work.

Let's look at another scenario:

const globalName = "Mozilla";

function f() {
    function displayName() {
        console.log(globalName);
    }
    return displayName;
}

f()();

Like the first example, this inner function is still conceptually a closure since it refers to a non-local variable. It also still uses the implementation technique of a closure since it's really a function object that stores an environment. This inner function is even executed after its containing function has finished and returned. But still, this inner function doesn't need to use the implementation technique of a closure because this time the non-local variable is global. Global variables, of course, exist for as long as the program exists. We don't have to worry about them going out of scope and being destroyed. This too could be implemented in C and it would Just Work.

Let's look at one last example.

const globalName = "Mozilla";

function displayName() {
    console.log(globalName);
}

displayName();

This time there's no more nested functions. Just an ordinary global variable and an ordinary global function. Conceptually, this function is still a closure since it refers to a non-local variable. And this being JavaScript, it also still uses the implementation technique of a closure since it's really a function object that stores an environment. But -- obviously -- it doesn't need to use the implementation technique of a closure.

So what, then, defines a closure? Is it the implementation? Is a record storing the combination of a plain function and an environment the defining characteristic of a closure? If so, then every function in JavaScript -- literally every single one -- is a closure. But however technically true that may be, it isn't useful in day-to-day work and conversation. If you were in a JavaScript interview and said an ordinary global function was a closure, the interviewer would probably look at you funny. So then do we ignore the underlying implementation and instead say a function is a closure only if it couldn't be implemented as a plain function? That's what we in the JavaScript community have historically done, but it muddies the terminology in ways that wouldn't be accectable outside the world of JavaScript. If you were in a C++ interview, for example, and said display_name = [] () { cout << global_name; }; wasn't a closure because it didn't need to be, then that interviewer would look at you funny too.

So which is the right way to use the term? I don't have an easy answer for you here. Most times in conversation, I'll skirt the issue with phrases like "technically a closure" or "a closure is useful when..." to stay technically correct without digressing.