Muhammad Shahnewaz
AboutProjectsPosts
Muhammad Shahnewaz on GitHubMuhammad Shahnewaz on LinkedInMuhammad Shahnewaz on MediumMuhammad Shahnewaz on Email

Copyright © 2026 | All rights reserved.

← Back to posts

Understanding JavaScript Closures

18 May, 2026·4 min read
javascriptfundamentalswebdev
Table of Contents
  • What is a Closure?
  • Lexical Scope
  • Practical Examples
  • Common Patterns
  • Pitfalls to Avoid
  • Conclusion

What is a Closure?

A closure is a function that remembers the variables from its outer scope, even after the outer function has finished executing. Closures are one of the most powerful features in JavaScript.

Here is a simple example:

function createGreeter(name) {
  return function () {
    console.log(`Hello, ${name}!`);
  };
}
 
const greet = createGreeter('Shahnewaz');
greet(); // Hello, Shahnewaz!

The inner function closes over the name variable. Even though createGreeter has returned, the inner function still has access to name.

Lexical Scope

JavaScript uses lexical scoping — the scope of a variable is determined by where it is written in the code, not where it is called.

const outer = 'I am outside';
 
function demo() {
  console.log(outer); // Can access outer
}
 
demo();

Variables defined in an outer scope are available to all inner scopes. This chain of scopes is called the scope chain.

Key idea: Closures capture variables by reference, not by value. If the variable changes after the closure is created, the closure sees the new value.

Practical Examples

Data Privacy

Closures let you create private variables:

function createCounter() {
  let count = 0;
 
  return {
    increment() {
      count += 1;
      return count;
    },
    decrement() {
      count -= 1;
      return count;
    },
    getCount() {
      return count;
    }
  };
}
 
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.getCount();  // 2

The count variable is not accessible from outside. Only the returned methods can read or modify it.

Function Factories

You can use closures to create specialized functions:

function multiply(factor) {
  return function (number) {
    return number * factor;
  };
}
 
const double = multiply(2);
const triple = multiply(3);
 
double(5); // 10
triple(5); // 15

Event Handlers

Closures are commonly used in event handlers:

function setupButton(buttonId, message) {
  const button = document.getElementById(buttonId);
  button.addEventListener('click', function () {
    alert(message);
  });
}
 
setupButton('btn1', 'First button clicked');
setupButton('btn2', 'Second button clicked');

Each handler remembers its own message value.

Common Patterns

Closures show up in a lot of everyday JavaScript code. Here are some patterns you have probably already used without realizing it:

Data privacy — Keeps internal state hidden from the outside. You have seen this in createCounter().

Function factory — Builds new functions on the fly. Think of multiply(factor).

Memoization — Remembers past results so you do not redo the work. Common in cachedFetch(url).

Debouncing — Waits until things settle down before running. Used in debounce(fn, delay).

Module pattern — Groups related logic into a single unit. Like createModule().

Memoization Example

function memoize(fn) {
  const cache = {};
 
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache[key] !== undefined) {
      return cache[key];
    }
    const result = fn(...args);
    cache[key] = result;
    return result;
  };
}
 
const expensiveSquare = memoize(function (n) {
  console.log('Computing...');
  return n * n;
});
 
expensiveSquare(5); // Computing... 25
expensiveSquare(5); // 25 (cached, no "Computing...")

Pitfalls to Avoid

Loop Variable Capture

The classic closure-in-a-loop problem:

// Bad: all functions share the same `i`
for (var i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}
// Output: 3, 3, 3

Fix 1: Use let instead of var:

for (let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i);
  }, 100);
}
// Output: 0, 1, 2

Fix 2: Use an IIFE to create a new scope:

for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(function () {
      console.log(j);
    }, 100);
  })(i);
}
// Output: 0, 1, 2

Memory Leaks

Closures keep references to outer variables. If a closure is long-lived, those variables are never garbage collected.

Warning: Be careful with closures that reference large objects or DOM elements. Clean up references when they are no longer needed.

Conclusion

Closures are fundamental to JavaScript. They enable data privacy, function factories, memoization, and many other patterns. Understanding closures will make you a stronger developer.

Tip: If you can explain why createCounter() works, you understand closures.


Further reading:

  • MDN: Closures
  • JavaScript.info: Closures