JavaScript Closures and Scope Explained

A
Author
29 May, 2026 1 min read 731 views

A closure is a function that remembers the variables from the place where it was created, even after that outer function has returned. Closures are behind callbacks, React hooks, module patterns and much more.

Lexical scope

const greeting = 'Hello';

function outer() {
  const name = 'Asha';
  function inner() {
    console.log(`${greeting}, ${name}`); // inner can see outer's variables
  }
  inner();
}

Scope is decided by where code is written, not where it's called.

A closure keeps variables alive

function createCounter() {
  let count = 0;                 // private: nothing outside can touch it
  return {
    increment: () => ++count,
    current: () => count,
  };
}

const counter = createCounter();
counter.increment();
counter.increment();
counter.current(); // 2

Function factories

const formatter = (currency) => (amount) =>
  new Intl.NumberFormat('en-IN', { style: 'currency', currency }).format(amount);

const inr = formatter('INR');
const usd = formatter('USD');
inr(1500); // '₹1,500.00'

The classic loop bug

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i)); // 3, 3, 3 - one shared 'var i'
}

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i)); // 0, 1, 2 - a new 'i' each iteration
}

Stale closures in React

useEffect(() => {
  const id = setInterval(() => setCount(count + 1), 1000); // always sees the first 'count'
  return () => clearInterval(id);
}, []);

// Fix: use the updater form, which receives the latest value
setCount((c) => c + 1);

Memory

Closures keep their captured variables in memory. That's usually fine, but a long-lived listener that captures a huge object keeps it alive. Remove listeners and clear timers when you're done.

Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.