Understanding Closures in JavaScript
SkillVeris Team
Engineering Team

A JavaScript closure is a function bundled together with references to the variables from the scope in which it was defined, so it keeps access to them even after that scope has returned.
In this guide, you'll learn:
- Closures form automatically whenever a function is created inside another function and uses variables from the outer one.
- They enable data privacy: variables in the enclosing scope act like private state that only the inner function can read or change.
- Closures power common patterns including factory functions, function currying, memoization, and event handlers that remember context.
- A classic bug is using var in a loop, where every closure shares one variable; let fixes it by giving each iteration its own binding.
1What Is a Closure in JavaScript?
A closure is a function that remembers the variables from the scope where it was created, even after that outer scope has finished running. In JavaScript, every time you define a function inside another function, the inner function forms a closure over the outer function's variables — it keeps a live reference to them, not a copy.
This happens because functions in JavaScript carry their lexical environment with them. Where a function is written determines what it can see, and that visibility persists for the function's whole lifetime, which is what makes closures both powerful and occasionally surprising.
2A Simple Example
The clearest demonstration is a counter. The outer function defines a variable and returns an inner function that uses it. Even after the outer function has returned, the inner function still reads and updates that variable — proof the variable is being kept alive by the closure.
- function makeCounter() {
- let count = 0; // lives in the outer scope
- return function () {
- count += 1; // the closure keeps count alive
- return count;
- };
- }
- const next = makeCounter();
- next(); // 1
- next(); // 2 — state persists between calls
🔑The Core Idea
count is not destroyed when makeCounter returns because the returned function still references it. That surviving reference is the closure.
3Closures Create Private State
Because the outer variable is reachable only through the inner function, closures give you true data privacy — something JavaScript lacked for years before class private fields. Nothing outside can read or mutate the variable directly; the only access is through the functions you deliberately expose. This is the foundation of the module pattern.
- function createAccount(balance) {
- return {
- deposit: amount => (balance += amount),
- withdraw: amount => (balance -= amount),
- getBalance: () => balance,
- };
- }
- const account = createAccount(100);
- account.deposit(50); // 150 — balance is private, no direct access
Why This Matters
The balance variable cannot be set to a negative number from outside or overwritten by accident, because there is no reference to it except through the three methods. Encapsulation like this prevents whole categories of bugs.
4Common Closure Patterns
Closures underpin many everyday JavaScript patterns. Once you recognise the shape — a function returning or capturing another function — you see them everywhere in real codebases and libraries.
- Factory functions: makeCounter above produces preconfigured functions on demand.
- Currying: a function that returns another to capture arguments one at a time.
- Memoization: cache results in an enclosed object so repeat calls are instant.
- Event handlers: a callback that remembers which element or item it was set up for.
- Once wrappers: a function that runs its inner logic a single time using a captured flag.
Memoization Example
A cache object lives in the closure and survives between calls, so repeated inputs return instantly.
function memoize(fn) {
const cache = {}; // private, persists across calls
return key => cache[key] ?? (cache[key] = fn(key));
}5The Classic Loop Gotcha
The most famous closure bug involves creating functions inside a loop with var. Because var is function-scoped, every closure captures the same single variable, so by the time the callbacks run the loop has finished and they all see the final value. Switching to let, which is block-scoped, gives each iteration its own binding and fixes the problem.
- for (var i = 0; i < 3; i++) {
- setTimeout(() => console.log(i), 0); // logs 3, 3, 3
- }
- for (let i = 0; i < 3; i++) {
- setTimeout(() => console.log(i), 0); // logs 0, 1, 2
- }
⚠️Prefer let and const
This bug is a leading reason modern JavaScript avoids var. Using let or const gives each loop iteration a fresh binding, so closures capture the value you actually expect.
6Memory Considerations
Closures keep their captured variables alive as long as the closure itself exists, which is usually exactly what you want. The flip side is that a long-lived closure over a large object keeps that object in memory too. If you attach closures to global objects or long-lived event listeners, be deliberate about what they capture, and remove listeners you no longer need so their closures can be garbage collected.
💡Capture Only What You Need
A closure retains the whole scope it can see. If you only need one small value from a big object, extract it into its own variable before defining the inner function.
7Common Mistakes to Avoid
Closures are intuitive once the mental model clicks, but a handful of errors recur often enough to be worth naming.
- Using var in loops and expecting each callback to capture a distinct value.
- Assuming a closure copies variables — it references them, so later changes are visible.
- Creating closures inside hot loops needlessly, adding avoidable allocations.
- Leaking memory by keeping closures on long-lived listeners over large objects.
- Overusing closures for state that a simple class field or module would express more clearly.
8Key Takeaways
Closures reduce to a few reliable truths worth internalising.
- A closure is a function plus a live reference to the scope it was defined in.
- They keep outer variables alive after the outer function returns.
- Closures enable private state, factories, currying, and memoization.
- Use let, not var, in loops to avoid the shared-variable bug.
- Be mindful that closures retain what they capture, which affects memory.
9Frequently Asked Questions
Q: What is a closure in simple terms? A: A closure is a function that remembers the variables from the place where it was created, even after that place has finished executing. It lets an inner function keep using an outer function's variables long after the outer function has returned.
Q: Why are closures useful? A: They enable data privacy by keeping variables accessible only through chosen functions, and they power patterns like factories, memoization caches, currying, and callbacks that remember their context. Much of JavaScript's module and state-management code relies on closures.
Q: Do closures cause memory leaks? A: Not inherently, but they keep their captured variables alive for as long as the closure exists. If a long-lived closure holds a reference to a large object or is attached to a listener you never remove, that memory stays allocated. Remove unused listeners and capture only what you need.
Q: What is the difference between a closure and a regular function? A: Every function that accesses variables from an enclosing scope forms a closure over them — closures are not a separate feature but a natural consequence of lexical scoping. A function that uses only its own parameters and local variables simply does not close over anything.
Related Reading
Get The Print Version
Download a PDF of this article for offline reading.
About the Publisher
SkillVeris Team
Engineering Team
Our engineering writers turn abstract code concepts into hands-on, project-driven learning experiences.
View all postsRelated Posts
Never miss an update
Get the latest tutorials and guides delivered to your inbox.
No spam. Unsubscribe anytime.