Closures and scope are fundamental parts of JavaScript but often poorly explained and therefore the source of confusion to many developers. This is my take on explaining them to someone without a formal computer science background.

Before we begin, it’s worth noting that closures are really, really important. Many super-smart JavaScript experts think they’re literally the best idea ever in computer science (see Douglas Crockford, Kyle Simpson, etc). And they’re used everywhere so if you plan on writing JavaScript, you need to master this concept.

Scope

Scope is how a computer keeps track of all the variables in a program. It refers to the specific environment where a variable is accessible and can be used. JavaScript uses the lexical scoping approach which allows for scopes to be nested and therefore an outer scope encloses (hence closure) an inner scope. More on this soon!

There are two types of scope: global scope and local scope.

Global scope

If you declare a variable outside of any function or curly brackets {} then it is in the global scope.

var greeting = "Hello world!"; // global scope

A variable in the global scope is accessible anywhere in your program.

var greeting = "Hello world!"; // global scope

function myFunc() {
  console.log(greeting);
}

myFunc(); // "Hello world"

Think for a second about what happened. Our function myFunc had no reference to a variable called greeting, yet it was able to find greeting because it was declared in the global scope.

However, you should generally avoid declaring variables in the global scope because it can lead to naming confusion. Consider what would happen if you created a global variable greeting in your code and then months later someone else on your team created another global variable greeting. Which one would the computer use? Since programs can be quite large in size, it’s very easy to fall into this trap.

In summary, global scope means a variable is always available but try to avoid using it!

Local scope

In contrast to global scope, a variable available only in a specific part of your code is known as a local variable. It exists in a local scope. There are two ways to create local scope in JavaScript: function scope and block scope.

Function scope

Whenever you declare a variable within a function, that variable is only available within that function. Access to the variable is confined to the function’s local scope.

function sayHi() {
  var greeting = "Good morning";
  console.log(greeting);
}

sayHi(); // "Good morning"
greeting; // ReferenceError: greeting is not defined

Block scope

When you declare a variable between curly brackets {} with const or let it is only available within the block of those brackets. Hence the name block scope.

{
  let firstName = "William";
  var lastName = "Vincent"; // global scope!
  console.log(firstName + ' ' + lastName); // "William Vincent"
}

console.log(firstName); // ReferenceError: firstName is not defined
console.log(lastName); // "Vincent"

Note that when using var scope is only function-level. Even though we declared the variable lastName within a block, because we used var it was still declared in the global scope, not a block-level scope.

Separate function scope

Functions declared separately do not have access to each other’s scope. In the example below, we pass in the function myName and then try to access its local-scoped variable firstName.

function myName() {
  var firstName = "William";
}

function sayMyName() {
  myName();
  console.log(firstName);
}

sayMyName(); // ReferenceError: firstName is not defined

It doesn’t work. The local scope of each function is completely separate.

Closures and nested scope

However when a function is defined within another function the inner function has access to the variable scope of the outer function. This nesting of functions also results in a nesting of scope. The outer scope is said to “enclose” (hence the term closure) the scope of the inner function.

function outer() {
  var name = 'William';
  function inner() {
    console.log(name);
  }
  inner();    
}

outer(); // "William"

This works! If you understand why, then you’ll understand closures.

There are two functions here, outer and inner, and therefore two separate scopes for our variables. But when the inner function is invoked and tries to find name it’s nowhere to be found. So what does it do? It goes up a level in scope to the outer function and asks, Do you have a variable name?

The answer is yes! So the outer variable name is assigned by the compiler to the inner variable name.

The key point is that when we have inner nested functions and therefore nested scopes, the computer doesn’t give up when it can’t find a local variable name. Instead it moves up a level to the enclosing function scope and asks the question again. This one-way process continues until we reach the global, top-most function scope. If at that point we can’t find a variable, then a ReferenceError is thrown.

Closures in the wild

Once you understand what a closure is and how it works, you’ll realize that they’re everywhere in JavaScript code.

Have you ever heard of an IIFE (Immediately Invoked Function Expression)? It uses closures to prevent pollution of the global scope and is used in popular libraries such as jQuery.

Heard of the “module pattern” which allows public and private methods? It’s used everywhere too.

And if you’ve ever interviewed for a JavaScript position, it’s likely there’s been a question on timers or click handlers that really is a question about closures and scope. Consider the code below. What is the output?

for (var i=1; i<=5; i++) {
  setTimeout(function(){
    console.log("i: " + i);
  },i*1000);
}

You’re probably thinking 1 2 3 4 5 in one second increments, right? But really it’s 6 6 6 6 6! The reason is closures!

There are two functions in this example: the setTimeout() method from the window object and within it our internal anonymous function with the console.log statement. That means we have 3 nested scopes: the global scope, the setTimeout() scope, and then the anonymous function scope. When the anonymous function asks for the variable i in its local scope, it can’t find it. So it goes up a level to the setTimeout() function, which also doesn’t have a reference. Finally it looks in the global scope and does find a declared variable i, whose value is 6. There is only one i here, the global one at the end of the for-loop.

How do you fix this? One way is with an IIFE. In this example, the setTimeout function closes over line 2 (function(i)...) rather than the outer for-loop.

for (var i=1; i<=5; i++) {
  (function(i){
    setTimeout(function(){
      console.log("i: " + i);
    }, i*1000);
  })(i);
} // 1 2 3 4 5

Or you can use let since it’s block scoped and therefore there will be a new i on each iteration:

for (let i=1; i<=5; i++) {
  setTimeout(function(){
    console.log("i: " + i);
  }, i*1000);
}
// 1 2 3 4 5

Conclusion

Each time we create a new function, we also create a new scope. And since functions can be nested within one another, so too can our scope. The JavaScript compiler will always look first in the local scope for a variable name, and if unsuccessful will move up into any/all enclosing functions/scopes looking for the same variable.

For more on scope-related behaviors, check out this article on hoisting in JavaScript.




Want to improve your JavaScript? I have a list of recommended JavaScript books.