JS Foundations #3 — Closures & Lexical Scope

How closures work at the memory level, why they power module patterns, memoization, and function factories — plus the classic setTimeout-in-a-loop interview question.

11 min read
JavaScript
Fundamentals
Closures

TABLE OF CONTENTS
JS Foundations #3 — Closures & Lexical Scope

Closures are the reason inner functions "remember" variables from outer functions long after the outer function has returned. Think of a closure like a backpack — when a function is born, it packs up all the variables it can see in its surrounding scope and carries that backpack wherever it goes, even long after the outer function has finished.

They power module patterns, memoization, once(), and almost every callback you've ever written. Understanding the memory model behind closures changes how you write JavaScript.

Prerequisites: JS Foundations #1 — Variables & Scope, JS Foundations #2 — this


1. What is a Closure?

A closure is created when a function retains access to variables in its outer lexical scope even after that outer function has finished executing. In JavaScript, every function is a closure.

Let's walk through what happens step by step:

  1. outer() is called — it creates a local variable message and defines inner()
  2. inner() is returned — but it still "remembers" message from outer's scope
  3. outer() finishes — normally its variables would be destroyed, but...
  4. inner() still holds a reference to message, so the JS engine keeps it alive on the heap
  5. When fn() is called later, it can still access message as if outer() never ended

Loading editor...

outer() returned. Its stack frame is gone. But message still lives — on the heap — because inner still references it. The garbage collector won't touch message as long as fn exists.


2. The Scope Chain in Closures

Closures don't capture a snapshot — they hold a live reference to the outer variable. If the variable changes before the inner function runs, the inner function sees the latest value:

Loading editor...

Every closure sees 3 because var i is function-scoped — there's only one i, and by the time the closures run, the loop has finished and i is 3.

The fix with letlet creates a fresh binding per iteration:

Loading editor...

Or using an IIFE to create a new scope for each iteration (the pre-ES6 fix):

Loading editor...


3. Private State with Closures

The most practical use of closures: encapsulating private data. No class, no fields — just functions.

Loading editor...

This is the module pattern — every createCounter() call creates a new, independent closure with its own private count:

Loading editor...


4. Closure for Memoization

Cache expensive function results so repeated calls with the same input are instant:

Loading editor...


5. once() — Ensure a Function Runs Only Once

A useful pattern using closures to prevent a function from being called more than once:

Loading editor...


6. Function Factories

Closures can produce specialized functions — think of it as "creating a function from a template":

Loading editor...

This is the core of currying and partial application — covered in depth in a later article.


7. The Loop + setTimeout Closure Bug

A common task: log 1, 2, 3 at one-second intervals. Let's see what goes wrong and why.

Loading editor...

The loop races through in microseconds. By the time the first setTimeout fires (500ms later), i is already 4 (the value that made the loop stop). All three callbacks see that same 4.

Loading editor...

Loading editor...

All three approaches produce the same output. The key insight: closures don't capture values; they capture variable references. let solves it by creating a fresh variable per iteration. IIFE solves it by creating a fresh scope with a copy of the value.


8. Releasing Closures for Garbage Collection

As long as a reference to the inner function exists, the closure's variables stay alive. To free that memory, set the reference to null:

Loading editor...

This matters when you attach closures to DOM elements that may be removed, or when you cache closures in long-lived data structures.


9. Closure Performance Tip

Closures keep variables alive. If you capture a large object but only need a small part, you keep the entire object in memory:

Loading editor...


Key Takeaways

  • A closure is a function + its lexical environment — it retains access to outer-scope variables even after the outer function returns.
  • Closures hold live references, not snapshots — the classic for + var + setTimeout bug.
  • Closures enable private state without classes: counters, caches, once(), module patterns.
  • Every function in JavaScript is a closure. The question is just what it closes over.
  • Be mindful of memory — closing over a large object keeps the whole object alive.

Next: JS Foundations #4 — Prototypes & Inheritance__proto__ vs prototype, what new really does, and how class extends is sugar over the prototype chain.


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI