What are Closures in JavaScript?

JavaScript

readTime

6 min

What are Closures in JavaScript?

JavaScript is a language with incredible capabilities.
One feature that often sparks excitement among developers is closures.

For beginners, closures can seem complex.
In this article, I’ll explain what closures in JavaScript are, how they work, and why they are so important.

We’ll use simple examples that will help you understand this concept and apply it in your own code.

What are Closures in JavaScript?

Let’s start with the basics – what are closures in JavaScript?

A closure is a mechanism where an inner function has access to the variables defined in its outer function, even after the outer function has finished executing.

This provides a powerful feature, allowing you to "close over" certain data within a function and use it whenever needed.

Imagine you have a function that creates a set of data and returns another function that can access that data.

The outer variable is “closed over” and retained by the inner function, even after the outer function has completed.

How Does a Closure Work in JavaScript?

Example with a Closure

Let’s understand this with a simple example.
Here’s how a closure works:

javascript
function outerFunction() {
  let outerVariable = "I'm outside!";

  function innerFunction() {
    console.log(outerVariable); // 'I'm outside!'
  }

  return innerFunction;
}

const myClosure = outerFunction();
myClosure();

In this code, we have two functions – outerFunction and innerFunction.
When we call outerFunction, it returns the inner function.

The inner function innerFunction still has access to the outerVariable, even after outerFunction has completed.
This is what a closure does – the function remembers its environment (the variables available at the time it was created).

Lexical Scope – The Foundation of Closures

To understand how closures work, you need to know about lexical scope.

In JavaScript, variables are scoped based on where they are defined, not where they are called.

This means that an inner function has access to variables from its outer function because it was defined within it.

Example:

javascript
function sayHello() {
  let name = "John";

  if (true) {
    let name = "Adam";
    console.log(name); // "Adam"
  }

  console.log(name); // "John"
}

sayHello();

In this example, we have two variables named name, but each operates within its own lexical scope.
The variable inside the if block doesn’t affect the name variable defined in the main scope of sayHello.

The Difference Between var, let, and const

In JavaScript, there are three keywords for declaring variables: var, let, and const.

A variable declared with var can be accessed outside the block where it was declared.

On the other hand, variables defined with let and const have block scope, meaning they are accessible only within that block.

Example with var:

javascript
function testVar() {
  var counter = 1;

  if (true) {
    var counter = 2;
    console.log(counter); // 2
  }

  console.log(counter); // 2
}

testVar();

Example with let:

javascript
function testLet() {
  let counter = 1;

  if (true) {
    let counter = 2;
    console.log(counter); // 2
  }

  console.log(counter); // 1
}

testLet();

As you can see, let and const provide greater control over lexical scope, making the code more predictable and secure.

How to Use Closures in Practice?

Creating Counters

Closures are great for creating counters, which can close over a counter variable and increment it gradually:

javascript
function createCounter() {
  let count = 0;

  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

Here, the createCounter function creates a counter that closes over the count variable.
Each time you call counter(), the value of count is incremented.

Closures as Private Variables

One of the most interesting uses of closures is for creating private variables.
We can use functions to close over variables that are inaccessible from the outside:

javascript
function privateData() {
  let secret = "My secret";

  return {
    getSecret: function () {
      return secret;
    },
    setSecret: function (newSecret) {
      secret = newSecret;
    },
  };
}

const myData = privateData();
console.log(myData.getSecret()); // 'My secret'
myData.setSecret("New secret");
console.log(myData.getSecret()); // 'New secret'

Here, we have the privateData function, which closes over the secret variable, creating a mechanism for private variables with restricted access.

Pitfalls of Using Closures

Although closures are powerful, they can lead to issues, especially when used incorrectly.

One of the most common mistakes is mismanaging variables in a loop.

Example with var:

javascript
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

// Output: 3, 3, 3

The variable i is closed over and has a value of 3 by the end of the loop.

That’s why each setTimeout call logs that value.
Solution? Use let to create a new closure for each call:

javascript
for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

// Output: 0, 1, 2

Performance and Memory Management

Closures in JavaScript can sometimes lead to memory issues if used improperly.

When you create a closure, the outer variables are retained in memory as long as they’re needed.
If you have too many or too large closures, it can lead to unnecessary memory consumption.

Example:

javascript
function outerFunction() {
  var hugeArray = new Array(1000000).fill("Data");

  function innerFunction() {
    console.log(hugeArray.length);
  }

  return innerFunction;
}

const myClosure = outerFunction();
myClosure();

Here, the large hugeArray is closed over, meaning it will stay in memory as long as innerFunction has access to it.
To avoid this, close over only what is essential and remove references when they are no longer needed.

Using Closures in Node.js and Express.js

Closures are also crucial in environments like Node.js, where asynchronous operations are common.

With closures, we can ensure that asynchronous functions have access to the right variables, even when they run later.

Example with express.js:

javascript
const express = require("express");
const app = express();

function createLogger() {
  const start = Date.now();

  return function (req, res, next) {
    const duration = Date.now() - start;
    console.log(`Request time: ${duration}ms`);
    next();
  };
}

app.use(createLogger());

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(3000);

In this case, the createLogger function closes over the start variable, which holds the start time of the request.

This way, each middleware function has access to this value.

Summary

Closures in JavaScript allow you to close over variables within functions and access them at the right time.

Thanks to lexical scope in JavaScript, you can create more complex applications that work predictably and efficiently.

However, remember to use closures wisely – excessive use can lead to performance issues.

authorImg

Witek Pruchnicki

I passionately share knowledge about programming and more in various ways.