All Articles

Learn JavaScript Closures in 6 Minutes

cover-image

They’re stateful functions.

Closures are notoriously difficult to grasp, yet vital to advancing as a JavaScript developer. Understanding them can lead to more elegant code and better job opportunities.

I hope this post helps the concept stick as fast as possible.

BONUS: Closures aren’t JS specific! They’re a computer science concept that you’ll recognize anywhere else after learning them.

Functions Are Values Too

First off, understand that JavaScript supports first-class functions.

winnie-1

A fancy name, but it just means functions are treated like any other value. Values like strings, numbers, and objects.

What can you do with values?

Values can be variables

const name = 'Yazeed';
const age = 25;
const fullPerson = {
  name: name,
  age: age
};

Values can be in arrays

const items = ['Yazeed', 25, { name: 'Yazeed', age: 25 }];

Values can be returned from functions

function getPerson() {
  return ['Yazeed', 25, { name: 'Yazeed', age: 25 }];
}

Guess what? Functions can be all that too.

functions-can-do-that-too

Functions can be variables

const sayHi = function(name) {
  return `Hi, ${name}!`;
};

Functions can be in arrays

const myFunctions = [
  function sayHi(name) {
    return `Hi, ${name}!`;
  },
  function add(x, y) {
    return x + y;
  }
];

And here’s the big one…

Functions Can Return Other Functions

A function that returns another function has a special name. It’s called a higher-order function.

This is the foundation on which closures stand. Here’s our first example of a higher-order function.

function getGreeter() {
  return function() {
    return 'Hi, Jerome!';
  };
}

getGreeter returns a function. To be greeted, call it twice.

getGreeter(); // Returns function
getGreeter()(); // Hi, Jerome!

One call for the returned function, and one more for the greeting.

You can store it in a variable for easier reuse.

const greetJerome = getGreeter();

greetJerome(); // Hi, Jerome!
greetJerome(); // Hi, Jerome!
greetJerome(); // Hi, Jerome!

Get Some Closure

Now for the grand unveiling.

Instead of hardcoding Jerome, we’ll make getGreeter dynamic by accepting one parameter called name.

// We can greet anyone now!
function getGreeter(name) {
  return function() {
    return `Hi, ${name}!`;
  };
}

And use it like so…

const greetJerome = getGreeter('Jerome');
const greetYazeed = getGreeter('Yazeed');

greetJerome(); // Hi, Jerome!
greetYazeed(); // Hi, Yazeed!

fallout-hold-up

Look at this code again.

function getGreeter(name) {
  return function() {
    return `Hi, ${name}!`;
  };
}

We Used a Closure

The outer function takes name, but the inner function uses it later. This is the power of closures.

When a function returns, its lifecycle is complete. It can no longer perform any work, and its local variables are cleaned up.

Unless it returns another function. If that happens, then the returned function still has access to those outer variables, even after the parent passes on.

Benefits of Closures

why-do-i-care

Like I said, closures can level up your developer game. Here’s a few practical uses.

1. Data Privacy

Data privacy is essential for safely sharing code.

Without it, anyone using your function/library/framework can maliciously manipulate its inner variables.

A bank with no privacy

Consider this code that manages a bank account. The accountBalance is exposed globally!

let accountBalance = 0;
const manageBankAccount = function() {
  return {
    deposit: function(amount) {
      accountBalance += amount;
    },
    withdraw: function(amount) {
      // ... safety logic
      accountBalance -= amount;
    }
  };
};

What’s stopping me from inflating my balance or ruining someone else’s?

// later in the script...

accountBalance = 'Whatever I want, muhahaha >:)';

who-reset-my-balance-this-time

Languages like Java and C++ allow classes to have private fields. These fields cannot be accessed outside the class, enabling perfect privacy.

JavaScript doesn’t support private variables (yet), but we can use closures!

A bank with proper privacy

This time accountBalance sits inside our manager.

const manageBankAccount = function(initialBalance) {
  let accountBalance = initialBalance;

  return {
    getBalance: function() {
      return accountBalance;
    },
    deposit: function(amount) {
      accountBalance += amount;
    },
    withdraw: function(amount) {
      if (amount > accountBalance) {
        return 'You cannot draw that much!';
      }

      accountBalance -= amount;
    }
  };
};

And perhaps use it like so…

const accountManager = manageBankAccount(0);

accountManager.deposit(1000);
accountManager.withdraw(500);
accountManager.getBalance();

Notice I can’t directly access accountBalance anymore. I can only view it through getBalance, and change it via deposit and withdraw.

How’s this possible? Closures!

Even though manageBankAccount created the accountBalance variable, the three functions it returns all have access to accountBalance via closure.

i-wish-my-bank-did-that

2. Currying

I’ve written on currying before. It’s when a function takes its arguments one at a time.

So instead of this…

const add = function(x, y) {
  return x + y;
};

add(2, 4); // 6

You can curry add by leveraging closures…

const add = function(x) {
  return function(y) {
    return x + y;
  };
};

And you know that the returned function has access to x and y, so you could do something like this…

const add10 = add(10);

add10(10); // 20
add10(20); // 30
add10(30); // 40

Currying’s great if you’d like to “preload” a function’s arguments for easier reuse. Again, only possible through JavaScript closures!

3. React Developers Use Closures

If you’ve kept up with React news, you heard that they released hooks last year. The most confusing hook, useEffect, relies on closures.

This article won’t have a full React tutorial, so I hope the example’s simple enough for all.

Here’s the important part…

function App() {
  const username = 'yazeedb';

  React.useEffect(function() {
    fetch(`https://api.github.com/users/${username}`)
      .then((res) => res.json())
      .then((user) => console.log(user));
  });

  // blah blah blah
}

Change username in the code, notice that it will fetch that username and log the output to the console.

This is closures once again. username is defined inside the outer function, but useEffect’s inner function actually uses it.

Summary

  1. Functions are values too.
  2. Functions can return other functions.
  3. An outer function’s variables are still accessible to its inner function, even after the outer has passed on.
  4. Those variables are also known as state.
  5. Therefore, closures can also be called stateful functions.

Thanks for reading

For more content like this, check out https://yazeedb.com. And please let me know what else you’d like to see! My DMs are open on Twitter.

Until next time!