JavaScript · How JavaScript Works

Closures

A closure is a function that remembers the variables from the place where it was created. It sounds abstract, but you use closures every time you write a callback.

Beginner 5 min Lesson 20 of 49 Runnable examples
Before you start:FunctionsScope
0/49 in JavaScript
On this page · 5 min · Beginner

Learning objectives#

  • Define a closure in plain language
  • Create private state with a closure
  • Build function factories
  • Recognise closures in event handlers and React hooks

Explanation#

When a function is created inside another function, it keeps access to the outer function’s variables — even after the outer function has returned. That combination of a function and its remembered variables is a closure.

Closures let you hide data. Variables inside the outer function cannot be reached from outside; only the returned inner functions can read or change them.

Every callback you pass to setTimeout, addEventListener or Array.map is a closure over the variables in scope where you wrote it.

Code example: A counter with private state#

JavaScriptA counter with private state Runnable
function createCounter() {
  let count = 0; // private: not accessible from outside


  return {
    increment() { count++; return count; },
    reset() { count = 0; },
  };
}


const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
counter.reset();
console.log(counter.increment()); // 1
console.log(counter.count);       // undefined
Expected output
1
2
1
undefined
Opens this example in the full playground

Step-by-step explanation#

  1. createCounter() declares count and returns an object with two methods.
  2. After createCounter() returns, count would normally disappear — but increment and reset still reference it, so it is kept alive.
  3. Each call to increment() updates the same remembered count.
  4. counter.count is undefined: count is not a property, it is a captured variable only the methods can reach.
  5. Calling createCounter() again would create a brand-new, independent count.

Real-world use case#

Debounce functions, memoisation caches, module patterns and React hooks all rely on closures. A useState setter in an event handler works because the handler closes over component variables.

Common mistakes#

Stale closures

A callback captures the variables from when it was created. In React, an interval created once can keep reading an old state value; use the functional updater setCount(c => c + 1).

Accidental memory retention

A long-lived closure (e.g. a global event listener) keeps every captured variable alive. Remove listeners you no longer need.

Best practices#

  • Use closures to encapsulate state instead of globals
  • Keep captured data small in long-lived callbacks
  • Prefer let in loops so each iteration gets its own binding
  • Clean up listeners and timers

Remember this#

A closure = a function + the variables it remembers.

Inner functions keep access to outer variables.

Used for private state and callbacks.

Practice exercise#

Your task: Write makeMultiplier(factor) that returns a function multiplying its argument by factor. Create double and triple.

Show solution
JavaScript Runnable
function makeMultiplier(factor) {
  return n => n * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5), triple(5)); // 10 15
Opens this example in the full playground

Mini quiz#

Closures quizPredict the output Question 1 of 2

What does this print?

function outer() {
  let x = 1;
  return () => ++x;
}
const f = outer();
f();
console.log(f());
Score so far: 0/0

Want more? Practise all JavaScript MCQs · JavaScript interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.