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:
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.
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:
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):
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
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
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:
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.
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
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.
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