Observing a closure

Let's say we have a social media app, like Twitter or Facebook, and we want each user to be able to like different posts. As a result, we have two things so far in our app: a like count, just a number stored in a variable. And a function to update it:

let likeCount = 0;

function handleLikePost() {
  likeCount = likeCount + 1;
}

handleLikePost();
console.log("like count:", likeCount);

Whenever a user likes a post, we call handleLikePost and it increments our like count by 1.

[Run code]

And this works because we know that functions can access variables outside of it. In other words, functions can access any variables defined in any parent scope.

But there's a problem with this code. Since likeCount is in the global scope, and not in any function, like count is a global variable. Global variables can be used (and changed) by any other bit of code or function in our app.

For example, what if after our function, we mistakenly set our likeCount to zero?

let likeCount = 0;

function handleLikePost() {
  likeCount = likeCount + 1;
}

handleLikePost();
likeCount = 0;
console.log("like count:", likeCount);

[Run code]

Naturally, our likeCount can never be incremented from 0.

So when only one function needs a given piece of data, it just needs to exist locally, that is, within that function. We've already covered the issue with leaving things in the global scope that don't need to be there.

So now let's bring likeCount within our function:

function handleLikePost() {
  let likeCount = 0;
  likeCount = likeCount + 1;
}

handleLikePost();
console.log("like count:", likeCount);

And note that there's a shorter way to write the line where we increment likeCount. Instead of saying likeCount is equal to previous value of likeCount and add one like this, we can just use the += operator like so:

function handleLikePost() {
  let likeCount = 0;
  likeCount += 1;
}

handleLikePost();
console.log("like count:", likeCount);

And for it to work as before and get like count's value, we also need to bring our console.log into the function as well.

function handleLikePost() {
  let likeCount = 0;
  likeCount += 1;
  console.log("like count:", likeCount);
}

handleLikePost();

[Run code] And it still works as before.

So now we users should be able to like a post as many times as they want, so let's call handleLikePost a few more times:

handleLikePost();
handleLikePost();
handleLikePost();

[Run code] But when we run this code, there's a problem. We would expect to see the likeCount keep increasing, but we just see 1 each time. Why is that? Take a second, look at our code and explain why our likeCount is no longer being incremented...

Let's look at our handleLikePost functions and how it's working. Every time we use it, we are recreating this likeCount variable, which is being initialized to 0. So no wonder we can't keep track of the count between function calls. It keeps being set to 0 each time and then it's incremented by 1 and that's it.

So we're stuck here. Our variable needs to live inside of the handleLikePost function, but we can't preserve the count.

We need something that allows us to preserve or remember the like count value between function calls.

What if we tried something that may look a little strange at first--what if we tried putting another function in our function:

function handleLikePost() {
  let likeCount = 0;
  likeCount += 1;
  function() {

  }
}

handleLikePost();

Here we're going to name this function addLike. The reason? Because it will be responsible for incrementing the likeCount variable now. And note that this inner function doesn't have to have a name. It can be an anonymous function. In most cases, it is. We're just giving it a name so we can more easily talk about it and what it does.

So addLike will now be responsible for increasing our likeCount, so we'll move the line where we increment into our inner function.

function handleLikePost() {
  let likeCount = 0;
  function addLike() {
    likeCount += 1;
  }
}

handleLikePost();

What if we were to call this addLike function in handleLikePost?

function handleLikePost() {
  let likeCount = 0;
  function addLike() {
    likeCount += 1;
  }
  addLike();
}

handleLikePost();

All that would happen is that addLike would increment our likeCount, but still the likeCount variable would be destroyed. So again, we lose our value.

But instead of calling addLike within its enclosing function, what if we called it outside of the function? This seems even stranger. And how would we do that?

We know at this point that functions return values. For example, we could return our likeCount value at the end of handleLikePost to pass it to other parts of of our program:

function handleLikePost() {
  let likeCount = 0;
  function addLike() {
    likeCount += 1;
  }
  addLike();
  return likeCount;
}

handleLikePost();

But instead of doing that, let's return likeCount within addLike and then return the addLike function itself:

function handleLikePost() {
  let likeCount = 0;
  return function addLike() {
    likeCount += 1;
    return likeCount;
  };
  // addLike();
}

handleLikePost();

Now this may look bizarre, but this is allowed in JS. We can use functions like any other value in JS. So a function can be returned from another function. And this is what enables us to call addLike from outside of its enclosing function.

But how would we do that? Think about this for a minute and see if you can figure it out...

First, to better see what's happening, let's console.log handleLikePost when we call it and see what we get:

function handleLikePost() {
  let likeCount = 0;
  return function addLike() {
    likeCount += 1;
    return likeCount;
  };
}

console.log(handleLikePost());

[Run code] Unsurprisingly, we get the addLike function logged. Why? Because we're returning it, after all.

So now, to call it, couldn't we just put it another variable. I mean, as we just said, functions can be used like any other value in JS. If we can return it from a function, we can put it a variable too. So let's put it in a variable called like:

function handleLikePost() {
  let likeCount = 0;
  return function addLike() {
    likeCount += 1;
    return likeCount;
  };
}

const like = handleLikePost();

And finally, let's call like. And we'll do it a few times and console.log each result:

function handleLikePost() {
  let likeCount = 0;
  return function addLike() {
    likeCount += 1;
    return likeCount;
  };
}

const like = handleLikePost();

console.log(like());
console.log(like());
console.log(like());

[Run code] And finally, our like count is preserved! Every time we call like, the likeCount is incremented from it's previous value.

So what in the world happened here? Well, we figured out how to call the addLike function from outside the scope it was defined in. We did that by returning the inner function from the outer one and storing a reference to it, named like, to call it.

How a closure works, line-by-line

So that was our implementation, of course, but how did we preserve the value of likeCount before function calls?

First, the handleLikePost(..) outer function is executed, creating an instance of the inner function addLike(..); that function 'closes' over the variable likeCount, that is one scope above. And then we called the addLike function from outside the scope it was defined in. We did that by returning the inner function from the outer one and storing a reference to it, named like, to call it.

And when the like(..) function finishes running, normally we would expect all of its variables to be garbage collected (removed from memory). We'd expect each likeCount to go away, but they don't.

What is that reason? Closure. Since the inner function instances are still alive (assigned to like), the closures is still preserving the countLike variables.

Function in function isn't a function in global scope

You would think that having a function written in another function, would just be like a function written in the global scope. But it's not. This is why closure makes functions so powerful, because it is a special property that isn't present in anything else in the language.

Variable lifetime

To better appreciate closures, we have to understand how JavaScript treats variables that are created. You might have wondered what happens to variables when you close your page or go to another page within an app. How long do variables live?

Global variables live until the program is discarded, for example when you close the window. They are around for the life of the program.

However, local variables have short lives. They are created when the function is invoked, and deleted when the function is finished.

So before, where likeCount was just a local variable, when the function was run. The likeCount variable was created at the beginning of the function and then destroyed once it finished executing.

function handleLikePost() {
  let likeCount = 0;
  likeCount += 1;
  // function() {

  // }
}

Closures are not snapshots; they keep local variables alive

function handleLikePost(step) {
  let likeCount = 0;
  return function addLike() {
    likeCount += step;
    return likeCount;
  };
}

const like = handleLikePost(1);
const doubleLike = handleLikePost(2);

like(); // 1
like(); // 2

doubleLike(); // 2
doubleLike(); // 4

Each instance of the inner addLike() function closes over both the likeCount and step variables from its outer handleLikePost function's scope. step remains the same over time, but count is updated on each invocation of that inner function. Since closure is over the variables and not just snapshots of the values, these updates are preserved.

So what does this code show to us--the fact that we can pass in dynamic values to change the result of our function? That they are still alive! Closures keep local variables alive from functions that should have destroyed them a long time ago.

In other words, they are not static and unchanging, like a snapshot of the variables value at one point in time--they are a direct link and preservation of the variable itself. So closures can actually observe (or make!) updates to these variables over time.

What is a Closure

Now that you see how a closure is useful, there are two criteria for something to be a closure, both of which you've seen here:

  1. Closures are a property of JavaScript functions, all functions. And only functions have closures. No other data type.
  2. To observe a closure, you must execute a function in a different scope than where that function was originally defined.

And why do closures matter? Based off of what we've seen, pause and take a stab at answering this question. Why do we care about closures as JS developers?

Why closures matters is because it allows us to 'remember' values, which is very powerful. We saw it right here in this example. What use is a likeCount that doesn't remember likes. You'll encounter this often in your JS career. You need to hold onto some value somehow and likely keep it separate from other values. What do you use? A function. Why? To keep track of data over time with a closure.

And with that, you're already a step ahead other developers. Most developers learn this concept of closure backwards. They're told about closure first and they have a problem understanding it because they think it's some sort of special event that takes place and they don't know why it matters. Why we even care about closures in the first place is that it helps us solve problems exactly like this one, where we need to 'remember' or keep track of certain values.