JavaScript Closures Explained With Real Examples
A closure is a function bundled together with its surrounding state — the lexical environment in which it was created.
A closure is a function bundled together with its surrounding state — the lexical environment in which it was created. In simpler terms, a closure gives you access to an outer function's variables from an inner function, even after the outer function has finished executing. Think of it as a backpack that a function carries around, containing all the variables that were in scope when the function was born.
You've probably used closures without realizing it. Every time you nest a function inside another function and reference variables from the outer scope, you're creating a closure. It's one of JavaScript's most powerful features, enabling patterns like data privacy, factory functions, and event handlers that "remember" context.
- Data Privacy: Closures let you create private variables that can't be accessed directly from outside, mimicking private fields in class-based languages.
- Function Factories: You can generate specialized functions on-the-fly that remember specific configurations or parameters.
- Event Handlers & Callbacks: Closures preserve context in asynchronous code, ensuring your callbacks have access to the right variables when they execute later.
- Performance Optimization: Techniques like memoization rely on closures to cache results without polluting the global scope.
- Module Patterns: Before ES6 modules, closures were the primary way to create truly encapsulated, reusable code modules.
When you declare a function inside another function, the inner function maintains a reference to the outer function's scope chain. JavaScript doesn't throw away those outer variables when the outer function returns — it keeps them alive as long as the inner function exists.
Here's the fundamental mechanism:
javascriptfunction outer() { const secretNumber = 42; function inner() { console.log(secretNumber); // inner "closes over" secretNumber } return inner; } const myFunction = outer(); myFunction(); // Logs: 42
Even though outer() has finished executing, myFunction (which is the returned inner function) still has access to secretNumber. That's closure in action.
One of the most practical uses of closures is creating private state. Here's a counter that nobody can tamper with directly:
javascriptfunction createCounter() { let count = 0; // This variable is private return { increment() { count++; return count; }, decrement() { count--; return count; }, getValue() { return count; } }; } const counter = createCounter(); console.log(counter.increment()); // 1 console.log(counter.increment()); // 2 console.log(counter.getValue()); // 2 console.log(counter.count); // undefined (private!)
The count variable is completely hidden. Users of your counter can only interact with it through the methods you've exposed. This is data encapsulation without classes.
Closures shine in asynchronous code. When you attach event listeners or set timers, closures preserve the context you need later:
javascriptfunction setupButtons() { const buttons = document.querySelectorAll('.action-btn'); buttons.forEach((button, index) => { button.addEventListener('click', function() { console.log(`Button ${index} was clicked`); // "index" is remembered via closure }); }); }
Without closures, you'd lose track of which button was clicked. Each event handler function closes over its specific index value from the loop iteration.
You can use closures to create specialized versions of functions with pre-configured behavior:
javascriptfunction createMultiplier(multiplier) { return function(number) { return number * multiplier; }; } const double = createMultiplier(2); const triple = createMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15
Each returned function remembers its own multiplier value. You've created a factory that produces custom functions on demand.
| Approach | Scope Pollution | Privacy | Memory | Use Case |
|---|---|---|---|---|
| Closure | None | Private by default | Minimal (only what's needed) | Encapsulated state, modules |
| Global Variable | High | Publicly accessible | Stays in memory indefinitely | Rare (config constants only) |
| Class Property | None | Can be private (ES2022+) | Instance-based | OOP patterns, complex state |
Closures give you the benefits of private state without the overhead of class syntax, making them ideal for simple stateful functions.
| Need | Reach for |
|---|---|
| Hide variables from outside access | Closure with private variables |
| Remember values across function calls | Closure that returns a function |
| Event handler that remembers context | Closure in loop or callback |
| Create multiple specialized functions | Function factory with closures |
| Cache expensive computation results | Closure-based memoization |
| Module with private helper methods | IIFE returning public API (closure) |
-
Closure in loops with
var: Usingvarin a loop creates a shared reference. All closures point to the same variable. Fix: Uselet(block-scoped) or create an IIFE to capture each iteration's value. -
Memory leaks from forgotten closures: If a closure references large objects or DOM elements that are no longer needed, it prevents garbage collection. Fix: Explicitly null out references you're done with, or limit closure scope.
-
Confusing closure with scope: A closure isn't just accessing a variable in scope — it's about retaining access after the outer function has returned. Fix: Remember that closures preserve state beyond execution.
-
Expecting closures to capture values, not references: Closures capture variable references, not snapshots of values. If the variable changes, the closure sees the new value. Fix: Create a new scope or pass values explicitly when you need snapshots.
-
Overusing closures for simple tasks: Not everything needs a closure. If a regular function or parameter will do, use that. Fix: Reach for closures when you genuinely need persistent private state.
-
Forgetting closures hold onto entire scope chains: A closure doesn't just capture the one variable you use — it holds references to the entire outer scope. Fix: Keep outer function scopes lean, or extract closures to minimize what they capture.
💡 Think Like a Programmer: When you return a function from another function, ask yourself: "What does this inner function need to remember?" That's your closure. Master this mental model, and you'll write cleaner, more powerful JavaScript.
Keep Reading
Complete History of JavaScript
Read on to explore complete history of javascript — a beginner-friendly walkthrough by Codekilla.
10 Python One-Liners Every Beginner Should Memorise
A Python one-liner is exactly what it sounds like: code that accomplishes a meaningful task in a single line.
Search Engine Working: Crawler, Sitemap & robots.txt
Read on to explore search engine working: crawler, sitemap & robots.txt — a beginner-friendly walkthrough by Codekilla.
