Turning Learners Into Developers
Codekilla
CODEKILLA
JavaScript 5 min

JavaScript Closures Explained With Real Examples

A closure is a function bundled together with its surrounding state — the lexical environment in which it was created.

Rahul Chaudhary Wed Aug 26 2026
What is a Closure?

A closure is a function bundled together with its surrounding state — the lexical environment in which it was created. In simpler terms, a closure gives you access to an outer function's variables from an inner function, even after the outer function has finished executing. Think of it as a backpack that a function carries around, containing all the variables that were in scope when the function was born.

You've probably used closures without realizing it. Every time you nest a function inside another function and reference variables from the outer scope, you're creating a closure. It's one of JavaScript's most powerful features, enabling patterns like data privacy, factory functions, and event handlers that "remember" context.

Why It Matters
  • Data Privacy: Closures let you create private variables that can't be accessed directly from outside, mimicking private fields in class-based languages.
  • Function Factories: You can generate specialized functions on-the-fly that remember specific configurations or parameters.
  • Event Handlers & Callbacks: Closures preserve context in asynchronous code, ensuring your callbacks have access to the right variables when they execute later.
  • Performance Optimization: Techniques like memoization rely on closures to cache results without polluting the global scope.
  • Module Patterns: Before ES6 modules, closures were the primary way to create truly encapsulated, reusable code modules.
How Closures Actually Work

When you declare a function inside another function, the inner function maintains a reference to the outer function's scope chain. JavaScript doesn't throw away those outer variables when the outer function returns — it keeps them alive as long as the inner function exists.

Here's the fundamental mechanism:

javascript
function outer() {
  const secretNumber = 42;
  
  function inner() {
    console.log(secretNumber); // inner "closes over" secretNumber
  }
  
  return inner;
}

const myFunction = outer();
myFunction(); // Logs: 42

Even though outer() has finished executing, myFunction (which is the returned inner function) still has access to secretNumber. That's closure in action.

Real-World Example: Counter with Privacy

One of the most practical uses of closures is creating private state. Here's a counter that nobody can tamper with directly:

javascript
function createCounter() {
  let count = 0; // This variable is private
  
  return {
    increment() {
      count++;
      return count;
    },
    decrement() {
      count--;
      return count;
    },
    getValue() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getValue());  // 2
console.log(counter.count);       // undefined (private!)

The count variable is completely hidden. Users of your counter can only interact with it through the methods you've exposed. This is data encapsulation without classes.

Closures in Event Handlers

Closures shine in asynchronous code. When you attach event listeners or set timers, closures preserve the context you need later:

javascript
function setupButtons() {
  const buttons = document.querySelectorAll('.action-btn');
  
  buttons.forEach((button, index) => {
    button.addEventListener('click', function() {
      console.log(`Button ${index} was clicked`);
      // "index" is remembered via closure
    });
  });
}

Without closures, you'd lose track of which button was clicked. Each event handler function closes over its specific index value from the loop iteration.

Function Factories: Generating Custom Functions

You can use closures to create specialized versions of functions with pre-configured behavior:

javascript
function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

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

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

Each returned function remembers its own multiplier value. You've created a factory that produces custom functions on demand.

Closure vs. Global Variables
ApproachScope PollutionPrivacyMemoryUse Case
ClosureNonePrivate by defaultMinimal (only what's needed)Encapsulated state, modules
Global VariableHighPublicly accessibleStays in memory indefinitelyRare (config constants only)
Class PropertyNoneCan be private (ES2022+)Instance-basedOOP patterns, complex state

Closures give you the benefits of private state without the overhead of class syntax, making them ideal for simple stateful functions.

Quick Cheat Sheet
NeedReach for
Hide variables from outside accessClosure with private variables
Remember values across function callsClosure that returns a function
Event handler that remembers contextClosure in loop or callback
Create multiple specialized functionsFunction factory with closures
Cache expensive computation resultsClosure-based memoization
Module with private helper methodsIIFE returning public API (closure)
Common Mistakes
  • Closure in loops with var: Using var in a loop creates a shared reference. All closures point to the same variable. Fix: Use let (block-scoped) or create an IIFE to capture each iteration's value.

  • Memory leaks from forgotten closures: If a closure references large objects or DOM elements that are no longer needed, it prevents garbage collection. Fix: Explicitly null out references you're done with, or limit closure scope.

  • Confusing closure with scope: A closure isn't just accessing a variable in scope — it's about retaining access after the outer function has returned. Fix: Remember that closures preserve state beyond execution.

  • Expecting closures to capture values, not references: Closures capture variable references, not snapshots of values. If the variable changes, the closure sees the new value. Fix: Create a new scope or pass values explicitly when you need snapshots.

  • Overusing closures for simple tasks: Not everything needs a closure. If a regular function or parameter will do, use that. Fix: Reach for closures when you genuinely need persistent private state.

  • Forgetting closures hold onto entire scope chains: A closure doesn't just capture the one variable you use — it holds references to the entire outer scope. Fix: Keep outer function scopes lean, or extract closures to minimize what they capture.

💡 Think Like a Programmer: When you return a function from another function, ask yourself: "What does this inner function need to remember?" That's your closure. Master this mental model, and you'll write cleaner, more powerful JavaScript.

// was this useful?
Did this article answer your question?
// JavaScript · published by Codekilla
// related articles

Keep Reading