JavaScript Closures Explained — Interactive Examples | TryJS

Understand JavaScript closures with runnable counter examples. See how inner functions capture and remember their surrounding scope, and when closures cause bugs.

Overview

A closure is a function together with the lexical environment it was created in. In plain English: an inner function keeps access to the variables of the outer function even after the outer function has returned. Closures are how JavaScript implements private state, module patterns, function factories, partial application, and almost every callback-based API you've ever used.

How It Works

Lexical scoping is the foundation

In JavaScript, a function looks up variables by where it was written, not where it's called. An inner function can always read (and write) variables declared in any enclosing function.

The outer function can return — the scope lives on

When the outer function finishes, its local variables would normally be garbage-collected. But if an inner function still references them, the engine keeps that scope alive. The inner function 'closes over' those variables — hence the name.

Each call creates a fresh closure

Calling makeCounter() twice returns two independent counters with two independent count variables. They don't share state — each call produces a new lexical environment.

Closures are how private state is achieved

Variables inside the outer function are inaccessible from outside. The only way to read or mutate them is through the methods you return. This is the module pattern, and the basis of data hiding in pre-class JavaScript.

Common Mistakes

When to Use It

Closures are unavoidable in JavaScript — every callback is one. Use them deliberately for private state, function factories (e.g. makeAdder(5)), memoization caches, and encapsulating module-level state without a class.

Runnable Example

function makeCounter(initial = 0) {
  let count = initial;
  return {
    increment: () => ++count,
    decrement: () => --count,
    value: () => count,
  };
}

const counter = makeCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.value());     // 11

// Independent closures
const a = makeCounter();
const b = makeCounter(100);
a.increment(); a.increment();
b.increment();
console.log(a.value(), b.value()); // 2 101

// Function factory via closure
function makeAdder(x) {
  return y => x + y;
}
const add5 = makeAdder(5);
console.log(add5(3)); // 8

Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.

Frequently Asked Questions

What is a closure in JavaScript?

A closure is an inner function that retains access to the variables of its outer (enclosing) function, even after the outer function has returned. It's created automatically every time you define a function inside another function.

Why do closures matter?

They enable private state, callbacks with captured context, function factories, memoization, and the module pattern. In practice, every event handler and async callback you write is a closure over the surrounding scope.

Do closures cause memory leaks?

They can if you're not careful. A closure keeps its captured variables alive as long as the closure itself is referenced. If you store closures in long-lived structures that close over large objects, those objects won't be garbage-collected.

What is the 'classic closure loop bug'?

Using var in a for-loop creates a single variable shared by every iteration's callbacks — so all callbacks see the final value. The fix is to use let (which creates a new binding per iteration) or to wrap the callback in an IIFE.