Mastering JavaScript Closures – The Hidden Power Behind Functions

Emma GeorgeEmma George
15 Aug, 2025
Mastering JavaScript Closures – The Hidden Power Behind Functions

TABLE OF CONTENTS

Introduction

1 . What Exactly Is a Closure?

2 . Why Closures Exist – The Lexical Environment

3 . Closures in Real Life – Common Use Cases

4 . Closures and Loops – The Classic Gotcha

5 . Closures in Modern JavaScript

6 . Pros and Cons of Closures

7 . How Closures Affect Memory

8 . Advanced Patterns with Closures

9 . Closures in Interviews

Conclusion

Introduction

If you’ve been writing JavaScript for a while, you’ve probably stumbled upon the term closure. It’s one of those concepts that can feel mysterious at first, like an inside joke all senior developers get but no one fully explains.

Closures are everywhere in JavaScript, callbacks, event handlers, private variables, currying, memoization, you name it. In fact, you can’t write modern JavaScript without using closures, whether you realize it or not.

In this article, we’ll completely demystify closures. By the end, you’ll not only understand them, you’ll be able to use them with confidence, write cleaner code, and even impress your interviewer.

1 . What Exactly Is a Closure?

A closure is created when a function “remembers” variables from its outer scope—even after that scope has finished executing.

Think of a closure as a backpack: a function carries with it all the variables from the scope it was defined in, no matter where it’s called later.

function outer() {
  let counter = 0;

  function inner() {
    counter++;
    console.log(counter);
  }

  return inner;
}

const myFunction = outer();
myFunction(); // 1
myFunction(); // 2
myFunction(); // 3

Even though outer() has finished executing, the inner() function remembers counter. This memory is the closure.

2 . Why Closures Exist – The Lexical Environment

Closures exist because of lexical scoping in JavaScript.

  • Lexical scope means that a function’s scope is determined by where it’s physically written in the code.

  • When a function is created, it forms a relationship with the scope in which it was declared.

  • The scope chain is preserved even when the function is executed in a completely different place.

Key takeaway: The reason closures work is because JavaScript doesn’t just execute code—it also remembers the environment in which a function was created.

3 . Closures in Real Life – Common Use Cases

a) Private Variables

Closures let you hide data from the outside world, similar to private fields in OOP.

function createBankAccount(initialBalance) {
  let balance = initialBalance;

  return {
    deposit(amount) {
      balance += amount;
      console.log(`New balance: $${balance}`);
    },
    withdraw(amount) {
      if (amount <= balance) {
        balance -= amount;
        console.log(`New balance: $${balance}`);
      } else {
        console.log('Insufficient funds');
      }
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(100);
account.deposit(50);  // New balance: $150
console.log(account.getBalance()); // 150


Here, balance is private, it can’t be accessed directly, only through the returned methods.

b) Function Factories

Closures can create customized functions.

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

const double = multiplier(2);
const triple = multiplier(3);

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

c) Event Handlers

Closures help keep track of state in UI interactions.

function setupButton(id) {
  let count = 0;

  document.getElementById(id).addEventListener('click', function() {
    count++;
    console.log(`Button clicked ${count} times`);
  });
}

setupButton('myButton');

Even after setupButton is done, the event listener remembers count

d) Once Functions

Run a function only once.

function once(fn) {
  let called = false;
  let result;

  return function(...args) {
    if (!called) {
      result = fn.apply(this, args);
      called = true;
    }
    return result;
  };
}

const init = once(() => console.log("Initialized!"));
init(); // Initialized!
init(); // (nothing happens)

4 . Closures and Loops – The Classic Gotcha

Closures inside loops often trip developers up.

for (var i = 1; i <= 3; i++) {
  setTimeout(function() {
    console.log(i); // Logs 4, 4, 4
  }, 1000);
}

Why? Because var is function-scoped, and by the time the callbacks run, i is 4.

Fix:

  • Use let (block scope):
for (let i = 1; i <= 3; i++) {
  setTimeout(() => console.log(i), 1000); // 1, 2, 3
}
  • Or use an IIFE:
for (var i = 1; i <= 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 1000);
  })(i);
}

5 . Closures in Modern JavaScript

  • Closures aren’t just for old-school JS, they’re still essential today:

  • React Hooks use closures to remember state across renders.

  • Node.js modules use closures to keep variables private.

Functional programming techniques like currying and composition rely heavily on closures.

Example in React:

function Counter() {
  const [count, setCount] = React.useState(0);

  function increment() {
    setCount(prev => prev + 1); // closure over `prev`
  }

  return <button onClick={increment}>{count}</button>;
}

6 . Pros and Cons of Closures

✅ Pros:

  • Data encapsulation and privacy
  • Reduced global scope pollution
  • Powerful functional programming patterns

⚠️ Cons:

  • Overuse can lead to memory leaks (if large objects are kept alive unnecessarily)
  • Can make code harder to read for beginners
  • Risk of holding onto outdated variables if not managed properly

7 . How Closures Affect Memory

Closures can keep variables alive in memory longer than expected.

function bigDataClosure() {
  const hugeArray = new Array(1000000).fill('data');

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

const fn = bigDataClosure();
// hugeArray is still in memory because fn references it

Tip: If you no longer need a closure, set its reference to null to allow garbage collection.

8 . Advanced Patterns with Closures

a) Currying

Breaking a function into multiple single-argument functions.

function curryAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

console.log(curryAdd(1)(2)(3)); // 6

b) Memoization

Caching results for performance.

function memoize(fn) {
  const cache = {};

  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) return cache[key];
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}

const slowSquare = num => {
  console.log('Computing...');
  return num * num;
};

const fastSquare = memoize(slowSquare);
console.log(fastSquare(5)); // Computing... 25
console.log(fastSquare(5)); // 25 (from cache)

9 . Closures in Interviews

Closures are a must-know interview topic. Expect questions like:

  • Explain closures in simple terms.

  • Write a function that maintains state between calls.

  • Create a private counter.

  • Why might closures cause memory leaks?

  • How do closures differ in var vs let?

Conclusion

Closures are not just a quirky JavaScript feature—they’re a core part of how the language works. They enable:

  • State preservation across function calls
  • Data privacy
  • Powerful functional patterns like currying, memoization, and once functions
  • Cleaner, more modular, and maintainable code

When you understand closures, you unlock the ability to write JavaScript that’s not only functional but elegant.

Closures might feel mysterious at first, but they’re just functions carrying a “backpack” of variables from their original scope. Once you start recognizing and intentionally using them, you’ll see them everywhere, and you’ll be a much stronger developer for it.

Emma George

Emma George

Software Engineer

Senior Software Engineer