Hello! Welcome to our next lesson.
In our previous lessons, we've explored how the JavaScript engine manages code execution through execution contexts and the call stack, and how the modern toolchain transforms our source code for the browser. Now, we're returning to the core of the JS runtime to understand one of its most powerful and foundational features.
Today, we'll unravel the concept of closures. This topic directly builds on your understanding of execution contexts and scope. Our goal is to explain how closures are created, how they capture variables from their lexical scope, and the common memory implications. For someone with your background in computer science, understanding closures is about connecting the dots between lexical scoping, memory management, and function-first language design.
The Core Idea: Functions with Memory
At its heart, a closure is simply a function that "remembers" the environment—the scope—in which it was created. This allows a function to access variables from an outer (enclosing) function's scope, even after that outer function has finished running and its execution context is gone from the call stack.
This gives our functions a form of persistent memory, which is a powerful concept that differs from the typical lifecycle of local variables in many other languages.
To begin, let's watch an introduction that sets up the fundamental puzzle that closures solve.
JavaScript the Hard Parts: Closure, Scope & Execution Context
This first video segment from Codesmith's 'JavaScript the Hard Parts' series introduces the idea of functions having persistent memory. It will walk through the mechanics of returning a function from another function and pose the critical question: how can that returned function still access variables from an environment that no longer seems to exist?
Watch the video from the beginning up to 22:16. The first part (00:00 - 15:03) demonstrates the basic mechanism of returning a function from another. The second part (15:03 - 22:16) sets up the core challenge by exploring how an inner function accesses variables from its outer scope, leading us to question what happens when we call that inner function outside of its original context.
The "How": Lexical Environments and the Scope Chain
The video you just watched uses the excellent "backpack" analogy. The inner function carries a "backpack" of data from its home environment. Now, let's formalize this. In ECMAScript terminology, this "backpack" is called a Lexical Environment.
A Lexical Environment is a data structure that holds identifier-variable mappings (i.e., the names of your variables and their values). Crucially, it also holds a reference to its outer environment.

When your code refers to a variable, the JavaScript engine searches for it:
- First, in the current function's Lexical Environment.
- If not found, it follows the reference to the outer environment and looks there.
- It continues this process up the scope chain until it reaches the global environment.
This is what's known as lexical scoping: the accessibility of variables is determined by the position of the function in the source code, not by where it's called.
Let's dig into the formal definition.
Javascript Closures — In-Depth Explaination
The article 'Javascript Closures — In-Depth Explaination' by Dhruv Pathak provides a clear breakdown of the ECMAScript specification for Lexical Environments. This is the 'under-the-hood' mechanism you're interested in.
Read the sections titled 'Lexical Environment' and 'Scope Chain'. Focus on understanding the two key components of a Lexical Environment: the Environment Record (the variables) and the reference to the outer environment. This explains the technical foundation of the scope chain.
Now that you have the formal definition, let's see a visual representation of how this prevents the outer environment from being destroyed.
JavaScript Visualized - Closures
Lydia Hallie's 'JavaScript Visualized - Closures' video does an exceptional job of illustrating how these concepts connect to memory management and garbage collection.
Watch the video from 00:50 to 06:13. Pay close attention to: How a reference to an environment record prevents it from being garbage collected. How an inner function object holds an internal [[Environment]] property that references the outer environment record. How the outerEnv property of an environment record forms the scope chain during function execution.
So, to put it all together:
- When a function is created, it gets an internal property (conceptually
[[Environment]]) that links to the lexical environment it was created in. - When an outer function returns an inner function, the execution context of the outer function is popped from the call stack.
- However, because the returned inner function still holds a reference to its creation environment, that environment is not garbage collected. It is kept alive in memory (on the heap).
- This combination of the function and its "remembered" lexical environment is the closure.
The Closure in Action
Let's return to the Codesmith video to see this process play out. Now that you understand the formal mechanics, the "backpack" analogy will have a much deeper meaning.
JavaScript the Hard Parts: Closure, Scope & Execution Context
Now watch the segment that explains and demonstrates the 'backpack' in detail. You can now map this analogy directly to the concept of the persistent lexical scope reference you've just learned about.
Watch from 22:16 to 36:00. This part demonstrates what happens when the inner function is invoked later. Notice how the lookup for the counter variable doesn't go to the global scope, but instead to the 'backpack'—the preserved lexical environment of the outer function.
Memory Implications: State and Leaks
The fact that closures prevent their outer environments from being garbage collected is both a powerful feature and a potential source of bugs.
1. Independent State Preservation
Every time you call an outer "factory" function, you create a new, separate closure. Each new inner function gets its own private "backpack." This is a fundamental pattern for creating objects with private state in JavaScript.
JavaScript the Hard Parts: Closure, Scope & Execution Context
Let's continue with the Codesmith video to see how multiple calls to the outer function create independent closures, each with its own memory.
Watch from 44:39 to 51:24. This demonstrates that calling outer() twice creates two distinct functions (myNewFunction and anotherFunction) with two independent, persistent counter variables.
2. Unintentional Memory Retention (Leaks)
The power of closures can also be a pitfall. If a long-lived closure holds a reference to an outer scope that contains large amounts of data (e.g., a big array or a complex object), that data will be kept in memory, even if the closure itself doesn't use it. This can lead to memory leaks.
A classic example is the for loop gotcha with var.
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // What does this print?
}, 100);
}
Because var is function-scoped, not block-scoped, all three setTimeout callbacks close over the same i variable in the surrounding scope. By the time the callbacks execute, the loop has finished and i is 3. So, this code prints 3, 3, 3.
With let, which is block-scoped, a new lexical environment is created for each loop iteration. Each callback closes over a different environment, capturing the value of i (0, 1, and 2) for that specific iteration, giving the expected output.
This is a perfect example of how the specifics of lexical scoping directly impact the behavior of closures.
Scope & Closures - 33 JavaScript Concepts
For a comprehensive look at potential memory issues and best practices, let's consult the '33 JavaScript Concepts' article.
Read the section 'Common Mistakes and Pitfalls'. It covers the for loop problem in detail and, importantly, discusses memory leaks from closures that retain references to large data structures, offering best practices for cleanup.
Conclusion
You've now taken a deep dive into closures, connecting the intuitive idea of "functions with memory" to the underlying mechanics of lexical environments, scope chains, and garbage collection.
Key Takeaways:
- A closure is the combination of a function and the lexical environment within which that function was declared.
- Closures are created every time a function is defined. The function "closes over" its surrounding scope.
- This mechanism allows an inner function to access variables from its outer scope, even after the outer function's execution context has been destroyed. This is possible because the inner function's reference to the outer lexical environment prevents it from being garbage collected.
- Each call to a function factory creates a new and independent closure, enabling patterns like private data and stateful functions.
- The primary memory implication is that closures can cause variables to be retained in memory longer than expected. This can be a source of memory leaks if a closure unintentionally retains a reference to a large data structure.
What's Next?
With closures under your belt, you've mastered one of the three pillars of advanced JavaScript functions. In our next lesson, we will tackle another core concept that often causes confusion: the this keyword. We'll explore the four rules that determine its value in different contexts and how to control it explicitly.