JavaScript Closures — Complete Guide for Beginners

 

JavaScript · Advanced Concept

JavaScript Closures

One of the most powerful and misunderstood concepts in JavaScript. Once you understand closures — everything clicks.

I remember staring at closure code for hours – confused why a function could still "remember" variables after its parent function had finished running. Then it clicked. Closures are everywhere in JavaScript — in event handlers, callbacks, modules, and React hooks. This is the guide I wish I had.

What is a Closure?

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

In other words: an inner function always has access to the variables of its parent function. This "remembering" is called a closure.

JavaScript — Basic Closure
function outer() {
  let message = "Hello from outer!";

  function inner() {
    // inner can access outer's variable
    console.log(message);
  }

  return inner; // return the function
}

const myFunc = outer(); // outer() runs and finishes
myFunc(); // "Hello from outer!" — still works!

// WHY? Because inner() closes over message
// Even though outer() finished — message is remembered

Real World Example — Counter

The most classic closure example — a counter with private state. The count variable is hidden inside but the returned functions can still access it:

JavaScript — Counter with Closure
function createCounter() {
  let count = 0; // private variable

  return {
    increment() {
      count++;
      console.log(`Count: ${count}`);
    },
    decrement() {
      count--;
      console.log(`Count: ${count}`);
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
counter.decrement(); // Count: 1

console.log(counter.count); // undefined — private!
console.log(counter.getCount()); // 1

Factory Functions with Closures

You can use closures to create multiple independent instances — each with their own private state:

JavaScript — Factory Function
function createPlayer(name) {
  let score = 0; // each player has own score

  return {
    addPoints(points) {
      score += points;
      console.log(`${name}: ${score} points`);
    },
    getScore() {
      return score;
    }
  };
}

const player1 = createPlayer("Waheed");
const player2 = createPlayer("Ali");

player1.addPoints(10); // Waheed: 10 points
player2.addPoints(5);  // Ali: 5 points
player1.addPoints(20); // Waheed: 30 points

// Each player has independent score!
console.log(player1.getScore()); // 30
console.log(player2.getScore()); // 5

Where Closures Are Used

Use Case Why Closures? Example
Private Variables Hide data from outside Bank account balance
Factory Functions Create multiple instances createUser(), createCounter()
Event Handlers Remember context Button click handlers
Callbacks Keep data alive setTimeout, fetch
React Hooks useState uses closures useState, useEffect

Pro Tips

Pro Tip #1 — Every function is a closure
In JavaScript, every function automatically closes over the variables in its outer scope. You don't have to do anything special — it just happens automatically.
Pro Tip #2 — Closures create private variables
JavaScript has no built-in private keyword (before ES2022). Closures were the standard way to create truly private variables that can't be accessed from outside.
Pro Tip #3 — Watch memory usage
Closures keep variables in memory as long as the function exists. If you create many closures holding large data, it can cause memory issues. Always clean up when done.

Quick Cheatsheet

JS CLOSURES CHEATSHEET
// 1. Basic closure
function outer() {
  let x = 10;
  return () => console.log(x); // remembers x
}

// 2. Private variable
function makeSecret(secret) {
  return () => secret; // only way to get secret
}

// 3. Memoization with closure
function memoize(fn) {
  const cache = {};
  return (n) => {
    if (cache[n]) return cache[n];
    cache[n] = fn(n);
    return cache[n];
  };
}

// 4. Once function — runs only one time
function once(fn) {
  let called = false;
  return (...args) => {
    if (!called) {
      called = true;
      return fn(...args);
    }
  };
}
const init = once(() => console.log("Initialized!"));
init(); // "Initialized!"
init(); // nothing — already called
Next Lesson
JavaScript Classes — Object-Orientated Programming
View All JS Lessons →
Want daily web dev tips?
Follow Muhammad Waheed Asghar for daily JavaScript and CSS tips!