JavaScript Function Closures

Understanding how functions remember their environment

🔐 What are Closures?

A closure is a function that has access to variables from its outer (enclosing) scope even after the outer function has finished executing. It's like the function "remembers" its environment.


// Simple closure example
function outerFunction(x) {
    // This is the outer function's scope
    
    function innerFunction(y) {
        // Inner function has access to 'x'
        return x + y;
    }
    
    return innerFunction;
}

const addFive = outerFunction(5);
console.log(addFive(3)); // 8 (5 + 3)
                                    

Output:

8

Key Closure Concepts

🧠

Memory

Functions remember their environment

function outer() {
  let x = 10;
  return function() { return x; };
}
🔒

Private Variables

Create private data encapsulation

function counter() {
  let count = 0;
  return () => ++count;
}
🏭

Function Factory

Create specialized functions

function multiplier(factor) {
  return x => x * factor;
}
📦

Module Pattern

Create modules with public/private methods

const module = (function() {
  let private = 0;
  return { get: () => private };
})();

🔹 Basic Closure Example

Here's how closures work step by step:

function createGreeting(name) {
    // This variable is in the outer function's scope
    const greeting = `Hello, ${name}!`;
    
    // Inner function has access to 'greeting'
    function sayHello() {
        return greeting;
    }
    
    return sayHello;
}

// Create a closure
const greetJohn = createGreeting('John');
const greetMary = createGreeting('Mary');

// Even though createGreeting finished, the inner function remembers 'greeting'
console.log(greetJohn()); // "Hello, John!"
console.log(greetMary()); // "Hello, Mary!"

Output:

Hello, John!

Hello, Mary!

🔹 Counter with Closures

Closures are perfect for creating private variables:

function createCounter() {
    let count = 0; // Private variable
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter1 = createCounter();
const counter2 = createCounter();

console.log(counter1.increment()); // 1
console.log(counter1.increment()); // 2
console.log(counter2.increment()); // 1 (separate counter)
console.log(counter1.getCount()); // 2

Output:

1

2

1

2

🔹 Function Factory Pattern

Use closures to create specialized functions:

// Function factory for mathematical operations
function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

// Create specialized functions
const double = createMultiplier(2);
const triple = createMultiplier(3);
const quadruple = createMultiplier(4);

console.log(double(5)); // 10
console.log(triple(4)); // 12
console.log(quadruple(3)); // 12

// Function factory for validators
function createValidator(minLength) {
    return function(input) {
        return input.length >= minLength;
    };
}

const isValidPassword = createValidator(8);
const isValidUsername = createValidator(3);

console.log(isValidPassword('12345')); // false
console.log(isValidPassword('12345678')); // true
console.log(isValidUsername('jo')); // false
console.log(isValidUsername('john')); // true

Output:

10

12

12

false

true

false

true

🔹 Module Pattern with Closures

Create modules with public and private methods:

const bankAccount = (function() {
    // Private variables
    let balance = 0;
    let accountNumber = '12345';
    
    // Private function
    function validateAmount(amount) {
        return amount > 0 && typeof amount === 'number';
    }
    
    // Public API
    return {
        deposit: function(amount) {
            if (validateAmount(amount)) {
                balance += amount;
                return `Deposited $${amount}. New balance: $${balance}`;
            }
            return 'Invalid amount';
        },
        
        withdraw: function(amount) {
            if (validateAmount(amount) && amount <= balance) {
                balance -= amount;
                return `Withdrew $${amount}. New balance: $${balance}`;
            }
            return 'Invalid amount or insufficient funds';
        },
        
        getBalance: function() {
            return `Current balance: $${balance}`;
        },
        
        getAccountNumber: function() {
            return `Account: ${accountNumber}`;
        }
    };
})();

console.log(bankAccount.deposit(100)); // Deposited $100. New balance: $100
console.log(bankAccount.withdraw(30)); // Withdrew $30. New balance: $70
console.log(bankAccount.getBalance()); // Current balance: $70
// console.log(balance); // Error: balance is not accessible

Output:

Deposited $100. New balance: $100

Withdrew $30. New balance: $70

Current balance: $70

🧠 Test Your Knowledge

What is a closure in JavaScript?