Dataslope logoDataslope

Closures

The single most powerful idea in JavaScript — functions remembering the scope they were born in.

A closure is what you get when a function "remembers" the variables from the scope where it was defined, even after that scope has finished. Closures are one of the most powerful and useful ideas in JavaScript, and once you see them clearly, an enormous amount of real-world code starts to make sense.

The simplest closure

Watch this carefully:

Code Block
JavaScript ES2023+

makeGreeter runs, then returns. Yet the inner function hello remembers the value of greeting ("Hello") that was in scope when it was created. Even more impressively, howdy independently remembers its own greeting ("Howdy"). The two are completely separate.

That memory is the closure: the function hello "closed over" the variable greeting from makeGreeter's scope.

Each call to makeGreeter creates a separate "scope record" holding its own greeting. The returned function carries a reference to that record, keeping it alive.

Closures and state

Closures let a function carry private state that survives across calls, without exposing it to the outside world.

Code Block
JavaScript ES2023+

This is huge. The counter behaves like a tiny object with private data, built entirely out of a function. No class, no this, no global variable.

Why closures exist (the mechanics)

Earlier we said each call to a function creates a new scope, and the local variables vanish when the call returns. That second part is not quite true. A scope's variables vanish only when nothing still references them. If a returned function still points at those variables, the runtime keeps the scope alive in memory.

This is one of the gifts of JavaScript's garbage collector. You don't have to think about when the scope is freed; the runtime does the bookkeeping. You just write the function and trust it.

Closures over loop variables

A classic closure puzzle:

Code Block
JavaScript ES2023+

Because we declared i with let, each iteration of the loop gets its own fresh i. Each function closes over that iteration's i. The functions print 0, 1, 2, 3, 4 — just as you would expect.

Now try the same thing with the old var (which is function-scoped, not block-scoped):

Code Block
JavaScript ES2023+

Now every function shares the same i, the one declared by var at the top of the enclosing function (the script). By the time the loop ends, i is 5, and every closure sees that single, shared value.

This puzzle is one of the historical reasons we now use let for loop counters. It is also a real test of your scope and closure mental model.

Practical closures: configuration and partial application

Closures are how JavaScript expresses many practical patterns.

A configurable function

Code Block
JavaScript ES2023+

addUKTax and addUSCal are two specialised versions of the same underlying logic, each carrying its own rate. The rate is baked into the closure once and never has to be passed again.

Memoisation: remember past answers

Code Block
JavaScript ES2023+

The cache lives inside memoize's call and is only accessible through the returned function. It is a tiny, private piece of state. No part of the program can corrupt it from outside.

Closures and modules

Modules — collections of related functions and private data — are essentially a use of closures. The "module pattern" in pre-2015 JavaScript used closures explicitly:

Code Block
JavaScript ES2023+

This pattern — called the IIFE ("immediately invoked function expression") — was once everywhere. Modern code uses real modules (we will see them later) instead, but the underlying idea is still closures.

A multi-file closure example

Closures cross module boundaries cleanly: an exported function can carry private state inside it. The caller never has to see the state directly.

Code Block
JavaScript ES2023+

index.js has no way to read or reset the calls counter directly. The only way it can interact with the state is to call the returned function. That is encapsulation — and it's free, just from how closures work.

Closures, summarised

If you see a function holding onto values from where it was created, you are looking at a closure.

Challenge

Challenge
JavaScript ES2023+
Build your own counter factory

Write a function makeCounter(start) that returns an object with three methods:

  • increment() — increase the internal counter by 1 and return the new value.
  • decrement() — decrease the internal counter by 1 and return the new value.
  • value() — return the current value without changing it.

The starting value of the counter is the start argument (default 0). Each call to makeCounter should produce an independent counter.


QuestionSelect one

What is a "closure" in JavaScript?

A way to close (terminate) a function early

A keyword that hides a variable from the outside world

A function that "remembers" the variables from the scope where it was defined, even after that scope has finished

A way to copy values into a function

On this page