DEV Community

Cover image for What is closure, really?
CodeDino
CodeDino

Posted on

What is closure, really?

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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; }
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

const closure = closureWrapper(5);
console.log(closure.get()); // 5
closure.set(10);
console.log(closure.get()); // 10
Enter fullscreen mode Exit fullscreen mode

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));
  }
}
Enter fullscreen mode Exit fullscreen mode

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 */ }
  };
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Event handlers — Remember state in callbacks:

function setupButton(name) {
  return function() {
    console.log(`${name} was clicked`);
  }
}
button.addEventListener('click', setupButton('Submit'));
Enter fullscreen mode Exit fullscreen mode

Configuration — pre-configure functions:

function createValidator(minLength) {
  return (input) => input.length >= minLength;
}
let validatePassword = createValidator(8);
let validateUsername = createValidator(3);
Enter fullscreen mode Exit fullscreen mode

Module pattern — Create clean APIs:

const utils = (() => {
  let cache = {};
  return {
    memorize: (fn) => { /* uses cache */ },
    clearCache: () => cache = {}
  };
})();
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

A closure is when a function returns another function that still has access to variables from the original (local) scope. That's it.

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.

Collapse
 
codedino profile image
CodeDino • Edited

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!

Collapse
 
dotallio profile image
Dotallio

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?

Collapse
 
codedino profile image
CodeDino

Thank you for the feedback! Unfortunately, nothing comes to mind at the moment, but this is a good idea for another article.