Hiding The Details

Closure and Scope in Functions

Todd Motto by Todd Motto Google Developer Expert Icon Google Developer Expert
JavaScript Icon

Learn JavaScript the right way

Premium JavaScript courses to skyrocket your skills to the top.

5x Courses: Basics, Masterclass, HTML5 APIs, DOM, Testing with Jest.

The word “closure” often leads to puzzled faces and confusion, but is the name of a concept that exists in JavaScript.

To understand, we first need to discuss function scope and understand how to reference variables.

Each function in JavaScript creates new scope.

Child (inner) scopes can access parent variables, but parent (outer) scopes cannot reference child variables. It is a one-way street.

Example:

const cart = [];
console.log(cart); // ✅ access `cart`

function addToCart(drink) {
  cart.push(drink); // ✅ access `cart`
}

But not the other way round:

const cart = [];

function addToCart(drink) {
  let cartCount;
  cart.push(drink);
  cartCount = cart.length;
}

// ❌ Uncaught ReferenceError: cartCount is not defined
console.log(cartCount);

This is a fairly trivial example, but perfectly demonstates two different scopes; global scope, and function scope.

Notice how our child scope can reference cart, but the parent scope cannot access cartCount?

This example also introduces two more buzzwords for us - “scope chain” and “lexical scope”.

When the addToCart function runs, it first looks in the current scope for the cart variable. If it doesn’t find it, it moves up a level - in the scope chain.

If it finds the variable, the program continues, otherwise you’ll be thrown an error.

When we access a variable from a child scope to the parent scope, this is known as being available in the “lexical” scope.

A parent scope doesn’t have to be direct parent to child as well, our functions could be continuously nested:

const cart = [];
// ✅ `cart` available

function initializeCart() {
  // ✅ `cart` available
  function addToCart(drink) {
    // ✅ `cart` available
    cart.push(drink);
  }
}

With this in mind, you’re ready to learn about using closures in JavaScript - we’ve actually almost done it.

The only difference now is learning about the “lifecycle” of a function when implemented as a closure.

When a function has been called, it’s no longer needed and disposed of in memory.

A closure keeps it alive.

So, what we do is create a function that returns a function. The function we return can then access variables in the “closed” scope.

We can take our cart example, it’s actually bad practice what we’re doing because const cart is just floating around in the global scope. Anyone could touch it.

Let’s fix that, by closing up the cart variable, but keeping its reference alive by returning a function:

function initializeCart() {
  const cart = [];

  return function addToCart(drink) {
    cart.push(drink);
  };
}

const addToCart = initializeCart();

addToCart({ name: 'Lemonade', price: 299 });

So, what’s happening here?

First, we create a function that uses return function.

By doing this, when we call initializeCart(), instead of being disposed of it’s kept in memory because the returned function is assigned to const addToCart.

Therefore our internally scoped cart variable becomes not only protected, but when we call addToCart, an internal cart.push(drink) call is made.

And there you have it - that my friend is a JavaScript closure.

I like to remember that these functions “close over” variables, and by using a return statement to pass back another function, we keep it alive and any variables inside remain in living memory.

One last thing for the arrow function fans:

const initializeCart = () => {
  const cart = [];

  return (drink) => {
    cart.push(drink);
  };
}

const addToCart = initializeCart();
Functions into Functions

« Functions as Callbacks

Operations On Functions

Higher Order Functions »