Closure In Javascript

Giriraj lodhaGiriraj lodha
5 min read

What is a Closure?

A closure is a function that remembers the variables from its outer function even after the outer function has finished executing.

Simple Definition:

"A function inside another function that can access variables of its outer function even after the outer function has executed."


Understanding with an Example

Example Without Closure:

function outerFunction() {
    let message = "Hello, I am from outer function!";
}
console.log(message); // ❌ ERROR: message is not defined
  • Here, message is a local variable inside outerFunction().

  • Once outerFunction() runs, message is destroyed, so we can't access it outside.


Example With Closure:

function outerFunction() {
    let message = "Hello, I am from outer function!";

    function innerFunction() {
        console.log(message); // βœ… Accessing outer function's variable
    }

    return innerFunction;
}

const myClosure = outerFunction(); // outerFunction() executes
myClosure(); // Output: Hello, I am from outer function!

How it works?

  1. outerFunction() runs and creates message = "Hello, I am from outer function!".

  2. innerFunction() is created inside it and returns to myClosure.

  3. Even after outerFunction() finishes, myClosure() still remembers message.

  4. When we call myClosure(), it prints message even though outerFunction() is already finished!

πŸ‘‰ This is a closure! The inner function remembers its outer function’s variables.


Why Do We Need Closures?

Closures are useful in many real-world situations:

  1. Data Privacy (Encapsulation)

  2. Creating Function Factories

  3. Maintaining State in Asynchronous Code

  4. Avoiding Global Variables


Real-Life Examples of Closures

Example 1: Data Privacy (Encapsulation)

Closures help hide private data from outside access.

function bankAccount() {
    let balance = 1000; // Private variable

    return {
        deposit: function (amount) {
            balance += amount;
            console.log(`Deposited ${amount}, New Balance: ${balance}`);
        },
        withdraw: function (amount) {
            if (amount > balance) {
                console.log("Insufficient funds!");
            } else {
                balance -= amount;
                console.log(`Withdrawn ${amount}, Remaining Balance: ${balance}`);
            }
        }
    };
}

const myAccount = bankAccount();
myAccount.deposit(500);  // Deposited 500, New Balance: 1500
myAccount.withdraw(200); // Withdrawn 200, Remaining Balance: 1300
console.log(myAccount.balance); // ❌ Undefined! balance is private

πŸ’‘ Why?

  • balance is private and can't be accessed directly.

  • It can only be modified using deposit() and withdraw().


Example 2: Function Factory (Generating Functions Dynamically)

Closures allow us to create functions dynamically with different behaviors.

function multiplier(factor) {
    return function (num) {
        return num * factor;
    };
}

const double = multiplier(2);  // Creates a function that multiplies by 2
const triple = multiplier(3);  // Creates a function that multiplies by 3

console.log(double(5)); // Output: 10
console.log(triple(5)); // Output: 15

πŸ’‘ Why?

  • multiplier(2) returns a function that always multiplies by 2.

  • multiplier(3) returns a function that always multiplies by 3.

  • The inner function remembers the factor from the outer function!


Example 3: Maintaining State in Asynchronous Code

Closures are useful when handling asynchronous operations like setTimeout.

function counter() {
    let count = 0;

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

const increment = counter();

increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3

πŸ’‘ Why?

  • The inner function remembers count even after counter() is finished.

  • Every time we call increment(), count keeps increasing!


Common Interview Questions on Closures

Q1: What will be the output of this code?

function outer() {
    let x = 10;

    return function inner() {
        console.log(x);
    };
}

const closureFunc = outer();
closureFunc();

βœ… Output: 10
πŸ’‘ Why? The inner function remembers x from outer().


Q2: What will be the output of this code?

function createCounter() {
    let count = 0;

    return {
        increment: function () {
            count++;
            console.log(count);
        },
        decrement: function () {
            count--;
            console.log(count);
        }
    };
}

const counter = createCounter();
counter.increment(); // ?
counter.increment(); // ?
counter.decrement(); // ?

βœ… Output:

1
2
1

πŸ’‘ Why? The count variable persists inside the closure.


Summary of Closures

βœ… Definition: A closure is a function that remembers variables from its outer scope.
βœ… How It Works? Inner functions "close over" variables from outer functions.
βœ… Why Use Closures?

  • Data Privacy (Encapsulation)

  • Creating Function Factories

  • Maintaining State in Asynchronous Code

  • Avoiding Global Variables


Can You Answer These?

1. Counter with Private State Problem:

Create a function createCounter() that returns an object with two methods:

  • increment(): Increases the counter by 1.

  • decrement(): Decreases the counter by 1.

  • getCount(): Returns the current count.

Constraints:

  • The count should be private and not accessible directly.
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
console.log(counter.getCount()); // 1

2. Secure Bank Account Problem:

Create a function bankAccount(initialBalance) that returns an object with:

  • deposit(amount): Adds amount to the balance.

  • withdraw(amount): Deducts amount if sufficient balance is available.

  • checkBalance(): Returns the current balance.

Constraints:

  • The balance should be private and only modifiable through the provided methods.
const myAccount = bankAccount(1000);
myAccount.deposit(500); // Balance: 1500
myAccount.withdraw(200); // Balance: 1300
console.log(myAccount.checkBalance()); // 1300
console.log(myAccount.balance); // Undefined (Should not be accessible)

3. Function Factory Problem:

Write a function createMultiplier(factor) that returns a function which multiplies a given number by the provided factor.

const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

4. Delay Execution with Closures Problem:

Create a function delayedLogger(message, delay) that logs message to the console after delay milliseconds. Use closures to retain the message value.

delayedLogger("Hello, World!", 2000); // Logs "Hello, World!" after 2 seconds

5. Event Listener with Closures Problem:

Create a function buttonClickHandler(buttonId, message) that adds a click event listener to a button. When clicked, the button should log the provided message.

buttonClickHandler("myButton", "Button clicked!");
// Clicking the button with id "myButton" should log "Button clicked!"

Bonus Challenge

Modify the bankAccount() function to support multiple accounts without conflicts.

Answers of Challenge

Visit my Github for see the soultion of these questions that i write ( direct link for answers)


Resources

I explore these websites, google search ai and chatgpt to explore this topic in deep.

11
Subscribe to my newsletter

Read articles from Giriraj lodha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Giriraj lodha
Giriraj lodha