loading...
Cover image for 2 Examples To Help You Understand JS Closures Once And For All 🙃

2 Examples To Help You Understand JS Closures Once And For All 🙃

harrison_codes profile image Harrison Reid Updated on ・3 min read

If you find this post useful, you can follow me on twitter, sign up to my mailing list or check out the other posts on my blog. I've also got a couple of active side projects that you might like to check out:

  • ippy.io - An app for creating beautiful resumes
  • many.tools - A collection of useful utilities for designers and devs

Closures are one of the classic “gotchas” in JavaScript. There are countless articles across the internet describing closures as something you absolutely need to understand to consider yourself a competent developer, or must know before your next job interview, etc etc.

Don’t get me wrong, understanding closures is very important. The thing is, I think there’s a reasonable chance you already understand them, but just don’t understand that you understand them 😉. But if you don’t, hopefully you will soon.

It’s just a hunch, but my guess is that a fair bit of the confusion around closures is simply due to terminology. That is, it can take some time to connect unfamiliar words like ”closure” and “lexical scope” with behaviour you’ve observed and perhaps already understand in your code.

Let’s take a look at a relatively straightforward example to test your current understanding.


1. Counter

Take a look at the code below, and try to figure out the answers to the two commented questions (without running the code).

function createCounter() {
  var count = 0
  function getNext() {
    count ++
    return count
  } 
  return getNext
}

console.log(count)
// ==> 1. What will this output?

const getNextNumber = createCounter()

const firstNumber = getNextNumber()
const secondNumber = getNextNumber()
const thirdNumber = getNextNumber()
const fourthNumber = getNextNumber()

console.log(
  firstNumber,
  secondNumber,
  thirdNumber,
  fourthNumber
)
// ==> 2. What will this output?

If you answered:

  1. ReferenceError (or if you knew this would be some kind of error)
  2. 1, 2, 3, 4

Congratulations! You understand closures!

There are two things you need to grasp from the code above:

  1. The count variable is not accessible anywhere outside of the createCounter() function.
  2. The count variable is accessible to any functions that are declared within the createCounter() function (where it was initially declared).

This is all that a closure is. Using a function (in our case createCounter() ) to close over a variable.

There is no way for the count variable to be accessed or set from anywhere else in our code, except via the function that we define and return from createCounter(), the getNext() function.

As you can see, getNext() (since it was declared inside createCounter()) maintains access to the count variable, and is able to increment and return it.

Let’s take a look at a slightly more complex example.


2. Election Day

Imagine we’ve been tasked with running an election. It’s a somewhat odd election, as voters will be casting their ballots from our JavaScript console.

We want a way to:

  1. Keep track of the votes
  2. Allow people to cast votes
  3. Retrieve the final results (in a secure, password protected way)

We could do something like this (but shouldn’t):

var candidateOneVoteCount = 0
var candidateTwoVoteCount = 0

function voteForCandidateOne() {
  candidateOneVoteCount ++
}

function voteForCandidateTwo() {
  candidateTwoVoteCount ++
}

function getResults(inputPassword) {
  if (inputPassword !== "password123") {
    throw new Error("Wrong password")
  }
  return {
    candidateOne: candidateOneVoteCount,
    candidateTwo: candidateTwoVoteCount
  }
}

Since the variables storing the candidates votes are defined in the global scope, anyone casting their vote could sneakily rig our election simply by running candidateTwoVoteCount = 1000000.

We need to keep our vote counts private. We only want it to be possible to change or retrieve these variables via the interface that we’ve defined. That is, via:

  • voteForCandidateOne()
  • voteForCandidateTwo()
  • getResults()

How can we achieve this? With a closure. Let’s refactor the code above to use a closure.

function createElection(password) {
  var candidateOneVoteCount = 0
  var candidateTwoVoteCount = 0

  function voteForCandidateOne() {
    candidateOneVoteCount ++
  }

  function voteForCandidateTwo() {
    candidateTwoVoteCount ++
  }

  function getResults(inputPassword) {
    if (inputPassword !== password) {
      throw new Error("Wrong password")
    }
    return {
      candidateOne: candidateOneVoteCount,
      candidateTwo: candidateTwoVoteCount
    }
  }

  return { 
    voteForCandidateOne, 
    voteForCandidateTwo, 
    getResults 
  }
}

const {
  voteForCandidateOne, 
  voteForCandidateTwo, 
  getResults 
} = createElection("password123")


console.log(candidateOneVoteCount)
// ReferenceError

console.log(candidateTwoVoteCount)
// ReferenceError

console.log(getResults("incorrectPassword"))
// Error: Wrong password

console.log(getResults("password123"))
// => { candidateOne: 0, candidateTwo: 0 }

voteForCandidateOne()
voteForCandidateOne()
voteForCandidateTwo()

console.log(getResults("password123"))
// => { candidateOne: 2, candidateTwo: 1 }

// Please never run a real election using code like this.

Our interface functions voteForCandidateOne(), voteForCandidateTwo() , getResults() are now declared within, and returned from createElection(). As they are declared in the same scope, they maintain access to the variables storing the vote count (candidateOneVoteCount & candidateTwoVoteCount).

It’s also worth noting that the functions also maintain access to the password argument that is provided when createElection() is called. This is later compared to the password provided in getResults() to validate access.


Let me know if anything here is unclear, and I'll do my best to explain it further! 🍻

Posted on by:

harrison_codes profile

Harrison Reid

@harrison_codes

My side projects haunt my dreams.

Discussion

markdown guide
 

I dont get in the first example why calling getNextNumber() increase variable count... I thought it would return 1,1,1,1... doesnt calling getNextNumber run createCounter() that set back count everytime to 0 does it?

 

Hey! Thanks for reading 😀

What you're expecting would the case if instead of:

const getNextNumber = createCounter()

I'd written:

const getNextNumber = createCounter

However, as the createCounter function is invoked when declaring the const getNextNumber, the return value of createCounter() is assigned to getNextNumber, rather than the function itself.

Since the getNext function is returned from createCounter, it is this function that's assigned to the const getNextNumber.

So when we call getNextNumber(), it is really the getNext function that's being run multiple times, not createCounter, which is only called a single time.

Does that make sense?

 

Yes, after reading your explanation I get where I was failing. You are assigning getNextNumber variable the getNext() function, not the return of it, or for better day you are assigning the return of createCount() function which is in fact getNext() function. Thanks to pointing that for it to become as I thought, it need to be assined just as createCounter (no brackets) instaed of createCounter(). Hope may help somebody like me who failed to grasp it 😁. Thanks again

 

Actually with createElection() you've effectively created an object. You could access it like this:

const obj = createElection()

 

Hey hey, thanks for reading 🍻

createElection() does indeed return an object. Which is why this line works to destructure the object into const values:

const {
  voteForCandidateOne, 
  voteForCandidateTwo, 
  getResults 
} = createElection("password123")

What's important here is that the returned object doesn't expose the vars candidateOneVoteCount & candidateTwoVoteCount directly - these are only accessible via the returned functions in that object. For example:

const election = createElection("password")

console.log(election.candidateOneVoteCount)
// => undefined

console.log(election.candidateTwoVoteCount)
// undefined

Similarly, you're not able to overwrite those variables by setting properties on the returned object:

const election = createElection("password")

console.log(election.getResults("password")
//  { candidateOne: 0, candidateTwo: 0 }

election.candidateOneVoteCount = 1000000
election.candidateTwoVoteCount = 2000000

// Still no votes for either candidate...
console.log(election.getResults("password")
//  { candidateOne: 0, candidateTwo: 0 }
 

Its an example of functional programming being able to represent OO programing (as in the SICP text book). Even if Javascript didn't support objects, using closures and currying can give you "objects" with pure functional constructs.