Recently I finally decided to dive deep into the concept of closures. There are plenty of good examples out there—in blogs, videos, and books. Here's one of them:
function closureWrapper(arg) {
let local = arg;
// Returns an anonymous function
return () => local;
}
const closure = closureWrapper(5)
// Calls the anonymous function immediately
console.log(closure()); // 5
The code looks simple, right? But under the hood, it can be surprisingly confusing—especially when people try to explain it with overly abstract jargon. That just leaves more questions than answers, making it hard for beginners—and even pros sometimes—to fully wrap their heads around the concept.
What is a Closure?
In simple terms:
A closure is when a function returns another function that still has access to variables from the original (local) scope. That's it.
But to be more technical:
Closure is not a special kind of function that has access to its surrounding scope, but rather a concept related to lexical scope - technically, any function creates a closure. It's an enclosed environment in which variables are captured. In the JavaScript world, we commonly use the word 'closure' to refer to functions that access variables from an outer function's scope and maintain that access even after the outer function has finished executing. You can read more about common misconceptions about closures in @jonrandy article:
Closures can be created using any function syntax. Let’s rewrite the previous example without using an arrow function, just to show another flavor:
function closureWrapper(arg) {
let local = arg;
return function () {
return local;
};
}
const closure = closureWrapper(5)
console.log(closure()); // 5
This version might look more verbose—it uses two return
statements.
But this version reveals the core idea more clearly:
We return a function that captures a local variable from its outer function.
A function enclosed inside another function—that’s where the name closure comes from.
You can't directly modify the local
value from outside, but you can create multiple closures with different values. Each one holds its own private copy, which allows for predictable behavior.
At the heart of this idea is encapsulation — nesting functions to create private scopes using just regular functional programming. It’s kind of like simulating private variables without using classes.
But what if you want to both read and write that inner value? You just need to return an object with some methods:
function closureWrapper(arg) {
let local = arg;
// Just return a plain object—nothing fancy
return {
// Use whatever names you like: get/set are just a convention
get: () => local,
set: (newValue) => { local = newValue; }
};
}
Usage:
const closure = closureWrapper(5);
console.log(closure.get()); // 5
closure.set(10);
console.log(closure.get()); // 10
Cool, right?
Why Should You Care?
Closures are super useful for encapsulation. Without them we would use global variables and write this kind of code:
let globalListeners = {};
function on(event, callback) {
if (!globalListeners[event]) globalListeners[event] = [];
globalListeners[event].push(callback);
}
function emit(event, data) {
if (globalListeners[event]) {
globalListeners[event].forEach(callback => callback(data));
}
}
With closures, you can group logic and data together in a controlled, predictable way:
function createEventEmitter() {
const listeners = {}; // Private to this instance
return {
on(event, callback) { /* uses listeners */ },
emit(event, data) { /* uses listeners */ }
};
}
Here’s a practical example using a pattern called a lens, often used in immutable state management:
// Lens pattern for immutable state updates
const userLens = {
name: {
get: (user) => user.name,
set: (newName, user) => ({ ...user, name: newName })
},
email: {
get: (user) => user.email,
set: (newEmail, user) => ({ ...user, email: newEmail })
}
};
const user = { name: "Alice", email: "alice@example.com" };
// And that's how Alice became Bob!
const newUser = userLens.name.set("Bob", user);
Or let’s say you want to build your own little reactive state manager:
function createStore(initialState) {
let state = initialState;
const listeners = [];
return {
// Shamelessly expose the state (immutably)
getState: () => ({ ...state }),
// Setting state updates only the changed keys
setState: (newState) => {
state = { ...state, ...newState };
listeners.forEach(fn => fn(state)); // Notify everyone!
},
// Subscriptions = gossip about state changes
subscribe: (fn) => listeners.push(fn)
};
}
const store = createStore({ count: 0 });
store.subscribe(state => console.log('State:', state));
store.setState({ count: 1 }); // Fires off subscribers
The only "new" thing in this example is the subscribe function.
It's just a callback manager that reacts when setState is called.
But yep—it works. And these simple closure-powered ideas are at the core of the frameworks we use every day.
Other Honorable Use-Cases for Closures
Factory functions — Same logic, different data:
function createCounter(start) {
let count = start;
return () => ++count;
}
let counter1 = createCounter(0);
let counter2 = createCounter(100);
Event handlers — Remember state in callbacks:
function setupButton(name) {
return function() {
console.log(`${name} was clicked`);
}
}
button.addEventListener('click', setupButton('Submit'));
Configuration — pre-configure functions:
function createValidator(minLength) {
return (input) => input.length >= minLength;
}
let validatePassword = createValidator(8);
let validateUsername = createValidator(3);
Module pattern — Create clean APIs:
const utils = (() => {
let cache = {};
return {
memorize: (fn) => { /* uses cache */ },
clearCache: () => cache = {}
};
})();
So basically, closures let you create template functions that can hold data and give you the ability to write reusable and configurable code.
Conclusion
A closure is just a function inside another function that references a value from the outer (local) scope. This allows us to encapsulate custom logic for reusability and clarity. So that’s a closure for you — hope this gave you enough insight to truly understand why closures are so useful in the JavaScript world!
Top comments (4)
Unfortunately, this is not correct. Nesting of functions is not required for the creation of closures, and closures are not functions (as is implied in your post).
ALL functions have an associated closure - regardless of how they were created, and ALL functions retain access to the variables in their lexical scope - regardless of when or where the function is executed. The closure is created at the time of the function's creation.
Misconceptions About Closures
Jon Randy 🎖️ ・ Sep 27 '23
Thank you for pointing out what closure truly is. I will make sure to add this distinction to my post for more clarity. Great article!
This really clears up closures - love the practical patterns, especially the state manager example. Do you have a favorite lesser-known use-case for closures?
Thank you for the feedback! Unfortunately, nothing comes to mind at the moment, but this is a good idea for another article.