Closures
Closures are one of JavaScript's most powerful features, but they often intimidate developers. If you've ever heard the word "closure" and immediately feared it because everyone said it's really difficult, don't worry! We've essentially already learned everything about closures when we studied scoping.
⚠️ WARNING: This is a tricky topic, so take your time to understand it fully!
What is a Closure?
A closure happens when an inner function has access to variables from an outer scope. We've actually been using closures without realizing it!
Simple Closure Example
const a = 1
function print() {
console.log(a) // Accessing variable from outer scope
}
print() // Prints: 1
This is technically a closure! The print function has access to the variable a from the outer scope.
Closures with Dynamic Values
What makes closures confusing is they access the current value of variables, not the value they had when the closure was created:
let a = 1
function print() {
console.log(a)
}
a = 2 // Change the value before calling the function
print() // Prints: 2 (not 1!)
The function reads whatever the most recent value of a is when we actually call the function.
Functions Inside Functions
The textbook example of closures involves functions inside other functions:
function outer(a) {
// This function returns another function
function inner(b) {
console.log(a) // From outer scope (outer function)
console.log(b) // From current scope (inner function)
}
return inner // Return the inner function
}
// Create a new function by calling outer
const newFunc = outer(1)
// Call the returned function
newFunc(2)
// Output:
// 1
// 2
How This Works Step by Step
- We call
outer(1), which creates a function that "remembers"a = 1 outerreturns theinnerfunction- We store this returned function in variable
newFunc - When we call
newFunc(2), it runsinner(2), which:- Logs
a(which is1from the outer scope) - Logs
b(which is2from the current call)
- Logs
Accessing Multiple Scope Levels
Closures can access variables from multiple outer scopes:
const c = 3 // Global scope
function outer(a) {
function inner(b) {
console.log(a) // From outer function scope
console.log(b) // From inner function scope
console.log(c) // From global scope
}
return inner
}
const newFunc = outer(1)
newFunc(2)
// Output:
// 1
// 2
// 3
The inner function has access to:
- Its own parameters (
b) - The outer function's parameters (
a) - Global variables (
c)
The Key Rule: One-Way Access
Remember the fundamental rule from scoping:
- ✅ Inner scopes can read outer scopes
- ❌ Outer scopes cannot read inner scopes
This is why closures work - the inner function can always access variables from outside.
Why Closures Matter
Closures are extremely useful for:
- Function Factories - Creating customized functions
- Callbacks - Maintaining access to variables in asynchronous code
Interview Questions
Closures are a favorite topic in JavaScript interviews. You might see code like this:
function outer(x) {
function inner(y) {
console.log(x + y)
}
return inner
}
const addFive = outer(5)
addFive(3) // What does this print?
Answer: It prints 8 because:
outer(5)creates a closure wherex = 5- Returns the
innerfunction that remembersx = 5 addFive(3)callsinner(3), which logs5 + 3 = 8
Practice Exercise
Create a function called createGreeter that:
- Takes a
greetingparameter (like "Hello" or "Hi") - Returns a function that takes a
nameparameter - The returned function should log the greeting + name
Solution
function createGreeter(greeting) {
return (name) => {
console.log(greeting + " " + name)
}
}
const sayHello = createGreeter("Hello")
const sayHi = createGreeter("Hi")
sayHello("Kyle") // "Hello Kyle"
sayHi("Sarah") // "Hi Sarah"