Closures are one of the most important concepts in JavaScript, yet they can be confusing for many developers. In this interactive walkthrough, we'll explore closures step by step with code examples.
A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. This is a powerful feature that enables many patterns in JavaScript.
What is a Closure?
Let's start with a simple example. Here's a basic closure:
Even though outerFunction has finished executing, innerFunction still has access to outerVariable. This is a closure!
function outerFunction() {
const outerVariable = "I'm outside!"
function innerFunction() {
console.log(outerVariable)
}
return innerFunction
}
const myFunction = outerFunction()
myFunction() // Prints: "I'm outside!"Closures and Scope
Closures have access to variables in their outer scope, even when the outer function has returned:
Each call to createCounter() creates a new closure with its own count variable. The inner function "remembers" the count variable from its outer scope.
function createCounter() {
let count = 0
return function() {
count++
return count
}
}
const counter = createCounter()
console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3Multiple Closures
When you create multiple closures, each one has its own independent state:
Notice how counter1 and counter2 maintain separate count variables. Each closure has its own lexical environment.
function createCounter() {
let count = 0
return function() {
count++
return count
}
}
const counter1 = createCounter()
const counter2 = createCounter()
console.log(counter1()) // 1
console.log(counter1()) // 2
console.log(counter2()) // 1
console.log(counter2()) // 2Closures in Loops
A common mistake is creating closures in loops. Here's what happens:
All three functions reference the same i variable. By the time they execute, i is 3. The solution is to use let or create a new closure:
With let, each iteration creates a new binding, so each closure captures a different i.
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i)
}, 100)
}
// Prints: 0, 1, 2Practical Example: Module Pattern
Closures enable the module pattern, which is useful for creating private variables:
The privateVariable is only accessible through the returned methods, creating a form of encapsulation.
function createModule() {
let privateVariable = 0
return {
increment: function() {
privateVariable++
},
decrement: function() {
privateVariable--
},
getValue: function() {
return privateVariable
}
}
}
const module = createModule()
module.increment()
module.increment()
console.log(module.getValue()) // 2
// module.privateVariable is not accessibleClosures with Parameters
Closures can also capture parameters from their outer function:
The inner function "remembers" the multiplier parameter from when createMultiplier was called.
function 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)) // 15What is a Closure?
Let's start with a simple example. Here's a basic closure:
Even though outerFunction has finished executing, innerFunction still has access to outerVariable. This is a closure!
Closures and Scope
Closures have access to variables in their outer scope, even when the outer function has returned:
Each call to createCounter() creates a new closure with its own count variable. The inner function "remembers" the count variable from its outer scope.
Multiple Closures
When you create multiple closures, each one has its own independent state:
Notice how counter1 and counter2 maintain separate count variables. Each closure has its own lexical environment.
Closures in Loops
A common mistake is creating closures in loops. Here's what happens:
All three functions reference the same i variable. By the time they execute, i is 3. The solution is to use let or create a new closure:
With let, each iteration creates a new binding, so each closure captures a different i.
Practical Example: Module Pattern
Closures enable the module pattern, which is useful for creating private variables:
The privateVariable is only accessible through the returned methods, creating a form of encapsulation.
Closures with Parameters
Closures can also capture parameters from their outer function:
The inner function "remembers" the multiplier parameter from when createMultiplier was called.
function outerFunction() {
const outerVariable = "I'm outside!"
function innerFunction() {
console.log(outerVariable)
}
return innerFunction
}
const myFunction = outerFunction()
myFunction() // Prints: "I'm outside!"Closures are a fundamental part of JavaScript. They enable powerful patterns like the module pattern, currying, and event handlers. Understanding closures will make you a better JavaScript developer and help you write more elegant and maintainable code.
Remember: A closure is created when a function is defined inside another function and has access to the outer function's variables, even after the outer function has returned.