Scope & Closures
Where a name lives — and how a function can carry its surroundings with it.
Prerequisites:Functions
Story — nested rooms with name tags
Picture a building with nested rooms. The innermost room has its own name tags. The room around it has more. The outermost room — the global lobby — has the rest. When code asks "where is x?", it looks in the current room first. Not here? Step outward. Still not here? Step out again. The first x it finds wins.
That nested-search rule is lexical scope. And here's the magic: when a function is born in an inner room, it doesn't forget that room — even after the function is carried out into the lobby and called there. The room's names travel with the function. That packaging is a closure.
Scope says
See it — the inner function carries its world
makeCounter defines count in its local scope and returns an inner function. makeCounter finishes — and yet, every time you call the inner function, it can still read and update count. That variable is alive only because something outside is holding the inner function.
Scope says
Try it — name a closure use case
Exercise. Name a real-world programming task where a closure is the simplest tool — and rewriting it without one would require a class or a global.
Reveal answer
Examples: a debounce wrapper that stores the most recent timer between calls; once(fn) that runs fn the first time and ignores subsequent calls; a click handler factory that takes a button name and returns a function that logs that name. All three are one-liners with closures and several lines without.
Code it — five languages, one captured variable
Python needs nonlocal to write to an outer variable (reads work without it). JavaScript and Go need no annotation at all. Java needs the variable to be effectively final, so we use an AtomicInteger. C++ captures must be explicit: [count] by value, [&count] by reference.
def make_counter():
count = 0 # captured by inner
def inc():
nonlocal count # without this, count is read-only
count += 1
return count
return inc
c = make_counter()
print(c(), c(), c()) # 1 2 3
print(c()) # 4 — count survived after make_counter returnedQuiz it — make it stick
No Dumb Questions
Real questions other learners asked on this page.
What is "lexical" scope?
Lexical means "from the source code" — a name resolves based on where the function was written, not where it was called. Almost every modern language is lexically scoped. (Some early languages had dynamic scope, which was confusing and slow.)Why do closures matter outside of fancy functional code?
Event handlers (a click handler that needs access to a counter), partial application, private state without classes, currying. Anywhere a function needs to remember something between calls without a class.What is the loop variable gotcha in closures?
In old JavaScript and some other languages, a closure created inside a loop captures the loop variable itself, not its value at iteration time — so all closures end up seeing the final value. Fixed by declaring with let (block-scoped) or capturing immediately.