[Part 1] JavaScript Closures: A Comprehensive Guide


Table of Content:


JavaScript closures are one of the most powerful and often misunderstood features of the language. They are essential for understanding how scoping and variable access work in JavaScript, and they enable powerful programming patterns and techniques. In this comprehensive guide, we'll dive deep into closures, starting from the basics and progressing to advanced use cases.

👉 Part 2

What are Closures?

A closure is a function that has access to variables from an outer (enclosing) function, even after the outer function has finished executing. The key idea behind closures is that they "close over" the variables from their outer scope, allowing them to remember and access those variables even when the outer function has completed.

Here's a simple example:

function outerFunction() {
  const outerVar = 'I am outside!';

  function innerFunction() {
    console.log(outerVar); // "I am outside!"
  }

  return innerFunction;
}

const myInnerFunc = outerFunction();
myInnerFunc(); // "I am outside!"

In this example, innerFunction is a closure because it has access to the outerVar variable from the outerFunction scope, even after outerFunction has finished executing. The myInnerFunc variable holds a reference to the innerFunction closure, which allows us to call it later and still have access to outerVar.

Closures and Lexical Scoping

To understand closures, we need to understand lexical scoping in JavaScript. Lexical scoping means that the scope of a variable is determined by its position in the source code (lexical environment) and not by the order of execution at runtime.

In JavaScript, every nested function has access to variables from its outer (enclosing) functions, following the scope chain. A closure is created when a nested function is defined and has access to variables from its outer scope, even after the outer function has finished executing.

function outerFunction(outerParam) {
  const outerVar = 'I am outside!';

  function innerFunction(innerParam) {
    const innerVar = 'I am inside!';
    console.log(outerParam); // "Hello, world!"
    console.log(outerVar); // "I am outside!"
    console.log(innerParam); // "Hello, inside!"
  }

  return innerFunction;
}

const myInnerFunc = outerFunction('Hello, world!');
myInnerFunc('Hello, inside!');

In this example, innerFunction has access to outerParam and outerVar from the outerFunction scope, as well as its own innerParam and innerVar variables. This is because innerFunction is a closure that "closes over" the variables from the outerFunction scope, and it has access to them even after outerFunction has finished executing.

Practical Use Cases for Closures

Closures have many practical use cases in JavaScript programming:

  1. Private Variables and Methods: Closures can be used to create private variables and methods in JavaScript, which can be useful for data encapsulation and information hiding.
  2. Function Factories: Closures allow you to create functions that generate other functions with pre-configured settings or data.
  3. Memoization and Caching: Closures can be used to cache and remember the results of expensive function calls, improving performance.
  4. Callbacks and Event Handlers: Closures are fundamental to the way callbacks and event handlers work in JavaScript, as they allow these functions to access and update data from their outer scopes.
  5. Partial Application and Currying: Closures enable techniques like partial application and currying, where functions are created with some arguments pre-filled or partially applied.
  6. Module Pattern: Closures are at the heart of the Module Pattern in JavaScript, a technique for creating reusable and self-contained modules with private state and public methods.

Private Variables and Methods

Here's an example of using closures to create private variables and methods:

function Counter() {
  let count = 0; // Private variable

  function incrementCount() {
    count++; // Access private variable
  }

  function decrementCount() {
    count--; // Access private variable
  }

  function getCount() {
    return count; // Return the private variable
  }

  return {
    increment: incrementCount,
    decrement: decrementCount,
    getCount: getCount,
  };
}

const myCounter = Counter();
myCounter.increment(); // Incrementing the private count
myCounter.increment(); // Incrementing again
console.log(myCounter.getCount()); // Output: 2

// Cannot access the private variable directly
console.log(myCounter.count); // undefined

In this example, the count variable is private to the Counter function and can only be accessed and modified through the incrementCount, decrementCount, and getCount functions, which are closures. These closures have access to the count variable from the outer Counter function, allowing us to create a counter object with public methods for incrementing, decrementing, and getting the count, while keeping the actual count value private.

Function Factories

Closures enable the creation of function factories, which are functions that generate other functions with pre-configured settings or data.

function createGreeter(greeting) {
  function greet(name) {
    console.log(`${greeting}, ${name}!`);
  }

  return greet;
}

const sayHello = createGreeter('Hello');
sayHello('John'); // Output: "Hello, John!"

const sayGoodMorning = createGreeter('Good morning');
sayGoodMorning('Jane'); // Output: "Good morning, Jane!"

In this example, createGreeter is a function factory that creates new greeting functions with a pre-configured greeting message. The greet function inside createGreeter is a closure that has access to the greeting parameter from the outer scope, even after createGreeter has finished executing. We can then call createGreeter with different greetings to create new greeting functions with different pre-configured greetings.

Memoization and Caching

Closures can be used to implement memoization and caching, which can significantly improve the performance of expensive function calls by storing and reusing previously computed results.

function fibonacci(n) {
  let memo = {}; // Closure to cache results

  function fib(num) {
    if (num in memo) {
      return memo[num]; // Return cached result
    }

    if (num <= 1) {
      return num;
    }

    memo[num] = fib(num - 1) + fib(num - 2); // Cache result
    return memo[num];
  }

  return fib(n);
}

console.time('fibonacci');
console.log(fibonacci(40)); // Output: 102334155
console.timeEnd('fibonacci'); // Fast due to memoization

In this example, the fibonacci function uses a closure (fib) to cache the results of previous Fibonacci number calculations. The memo object is a private variable inside the fibonacci function's scope, but it's accessible to the fib closure. When fib is called with a new number, it first checks if the result is already cached in memo. If not, it calculates the Fibonacci number recursively and stores the result in memo before returning it. This memoization technique can significantly speed up the Fibonacci calculation for larger numbers.

Advanced Closure Techniques

Now that we've covered the basics of closures and some practical use cases, let's explore some advanced closure techniques and patterns.

Currying with Closures

Currying is a technique where a function is transformed to take multiple arguments, one argument at a time. Closures play a crucial role in implementing currying in JavaScript. javascript

function multiply(a) {
  return function(b) {
    return function(c) {
      return a * b * c;

In this example, the multiply function takes the first argument a and returns a new function that takes the second argument b. This new function then returns another function that takes the third argument c. Each nested function is a closure that has access to the variables from the outer scopes.

When we call multiply(2), we get a new function multiplyByTwo that is a closure over a=2. Calling multiplyByTwo(3) returns another closure multiplyByTwoAndThree that has access to both a=2 and b=3. Finally, calling multiplyByTwoAndThree(4) computes and returns the result a * b * c, which is 2 * 3 * 4 = 24.

This currying pattern using closures allows us to create specialized versions of a function with some arguments pre-filled, leading to more expressive and reusable code.

👉 Continue here: Part 2