JS Demystified 05 — Closure
A humble attempt to demystify tricky concepts in JavaScript

JS Demystified Series

Intro
I hope you enjoyed my last post in the series, where we looked at execution context in great detail.
Now, it’s time to talk about closures.
If you are not comfortable with topics listed below, I recommend you to take a moment to refresh your knowledge there.
01 — Variable Hoisting
02 — Function Hoisting
03 — Scope
04 — Execution Context
Are you ready?
Great, let’s get to it.
Quiz — Test Your Knowledge
What would the below code print out?
The answer is Hello John
.
Yes, even though we altered the value of name
to 'Sam'
before we invoked sayHello
. It’s as if, the value of name
was captured before it was reassigned to 'Sam'
.
That is exactly what happened — this is closure in action.
Scope Chain — Take 2
Before we can talk more about closures, we need to take a step back and have a closer look at a scope chain, which I introduced to you in my last post.
Here’s an excerpt:
In addition to its own scope, every execution context has a reference to its outer scopes (if any), all the way up to the global scope. This chain of reference is what we call a scope chain.
In summary, for a given scope, a scope chain is created during the creation of a new execution context upon a function call, and it is a reference to variables that belong to outer scopes.
So we know what it is and when it’s created, but we still don’t exactly understand how a scope chain is created. For this, we need to introduce a new player to the story — [[scope]]
property of a function.
[[scope]] Property
Every function, on creation, is assigned an internal property [[scope]]
.
That means, when an execution context encounters a function creation (by function declaration or function expression), the function is given a [[scope]]
property. The value of the [[scope]]
property is the scope chain of the execution context, in which the function is created.
Then, the moment the function is invoked, a new execution context is created for that function call, and a scope chain is created at this time, collecting reference to all the locally scoped variables.
In addition, the scope chain inherits the the value of the [[scope]]
of the function invoked as a separate property. This is how a scope chain holds the reference to outer variables.
Remember, the [[scope]]
property is assigned to a function when it is created, not when it’s invoked. Then, when the function is invoked, the value of [[scope]]
becomes the reference to the other variable within the scope chain of the new execution context.
Okay, I’ve been talking a lot!
Let’s use below snippet to demonstrate.
- line 5: The global execution context encounters a function declaration of the
outer
function. Since the scope chain of the global execution context contains only the global object{ a: 'global' }
, theouter
function is assigned an internal[[scope]]
property:{ a: 'global' }
. - line 30: The function
outer
is invoked, a new execution context is created. The scope chain creates a reference to the locally scoped variableb
whose initial value isundefined
(remember hoisting?). In addition, the scope chain inherits the value of[[scope]]
as a reference to its outer scopes. - line 12: The execution context encounters a function declaration of the
inner
function. The current scope chain contains references to locally scopedb
, as well as to the outer variables, so it’d look like this:{ b: 'outer', outerScope: { a: 'global' }
, which now gets assigned to theinner
function’s[[scope]]
property. - line 27: The function
inner
is invoked and — same story — a new execution context is created. The scope chain collects reference to locally scopedc
. In addition, it inherits the value of[[scope]]
, i.e.{ b: 'outer', outerScope: { a: 'global' }
, as a reference to the outer scope. - line 22–24: When the time the JavaScript engine reaches the
console.log
s, the current scope chain contains{ c: 'inner', outerScope: { b: 'outer', outerScope: { a: 'global' } }
. Therefore we have access to alla
,b
, andc
.
Got it?
Now we understand:
- What is the
[[scope]]
property of a function. - When it is created.
- How it relates to a scope chain of the current and child execution context.
We’re finally ready to look at what exactly a closure is.
Closure
Let’s think about a scenario, deriving form our example above.
What if you want to invoke the inner
function later in the execution, rather than immediately calling it within the outer
function? What would console.log
s within the inner
function would print out?
We get the exactly same result as before.
- line 15: Even though the execution context of a function call
outer()
was removed after returning, the returnedinner
function (which was assigned to a variableinnerFunc
) retained its[[scope]]
property{ b: 'outer', outerScope: { a: 'global' }
. - line 17: Upon invocation of
innerFunc
, the scope chain of the new execution context inherited the[[scope]]
property as the reference to outer scopes. Therefore,a
andb
were both available to access from this execution context.
In fact, this reference to the outer variables, which is
- created on function creation
- available even after the outer execution context is removed
is what we call a closure.
By Reference, Not By Value
It is important to note that a closure captures the reference to the outer variables, not their values per se. All it remembers is where to find variables, which might be pointing to different values at different times.
In other words, the value of the variables inside a closure can be modified.
Consider a case where we change the value of the global variable a
, before we invoke the returned function.
- line 15: Assigned the returned function to
innerFunc
. - line 17: Modified the value of
a
toGLOBAL
. - line 19: At the time of invocation of
innerFunc
, the value ofa
isGLOBAL
. - line 9: Therefore when it was accessed
a
it gave usGLOBAL
.
Variable Lookups
But wait — you might think — why the heck was name
'John'
in the very first example?
If we follow the by reference principle we just discussed, surely name
points to 'Sam'
because we reassigned it before we called the returned function.
You’re right, but there’s a slight yet significant detail we’re missing.
Here, name
is passed to the greet
function as a parameter, which behaves as a local variable for the execution context of a function call.
- line 5: The global execution context encounters a function creation.
greet
’s[[scope]]
property is assigned the current scope chain{ name: 'John' }
. - line 17:
greet
is invoked with a parametername
, which currently points to'John'
, and the returned function is assigned tosayHello
. - line 8: The parameter
name
is treated as a locally scoped variable of the newly crated execution context. Further, the scope chain inherits the reference to outer variables stored in[[scope]]
property. - line 9: The current execution context encounters a function creation and its scope chain is
{ name: 'John', outerScope: { name: 'John' } }
. This becomes the[[scope]]
property of the returned function. - line 19: The global
name
is reassigned'Sam'
. - line 22:
sayHello
is invoked, the new execution context in created, and the scope chain inherits the[[scope]]
property of the invoked function. Globally scopedname
is now pointing to'Sam'
, whereas locally scopedname
still points to'John'
.
Variable lookups always starts from the local scope. Only when the JavaScript engine can’t find a variable within a local scope, does it go up one level in the scope chain to see if it’s defined there, and so on.
Therefore, when name
is access at line 13, the engine finds it in the local scope, the console.log
prints out Hello John
.
Further Reading
Here are a few of the sources I found useful and got some inspirations from:
Wrap-up
Alright, that’s it.
A closure is often considered an elephant in the room which no one wants to acknowledge and talk about. Hopefully I’ve managed to demystify it, by explaining what’s happening under the hood.
Please post any feedback, questions, or requests for topics. I would also appreciate 👏 if you like the post, so others can find this article too.
Thanks, and see you next time :)