JavaScript Exercises: Scope, Hoisting, and Closures
Getting Started with Scope
One of the early barriers that many people who are learning JavaScript encounter is the topic of variable scope. While the concept itself isn’t terribly difficult there are some behaviors that can prove confusing. The interconnection of scoping rules with closures, and the idea of hoisting for variable declarations can create some tricky gotcha situations. While working through these concepts myself, I put together a collection of examples that illustrate these various behaviors to help myself visualize and interpret what was happening in different contexts.
I wanted to make these available as a resource to practice reading and analyzing some of these situations; if you want to get right to it, skip down below and work through them at your leisure! Otherwise, I’ll quickly review some of the basics about JavaScript scoping, hoisting, and closures to front-load some of that knowledge before getting to the exercises.
Scoping
Scoping in JavaScript (and other programming languages) is commonly referred to as the current context of execution for your program. In other words, the context that determines which variables can be referenced at any given point in your program and which cannot. When a variable is not “in scope” then it cannot be used, if it is, then it can be used! Easy enough, right?
But what determines the scope? That is a bit trickier, but the important thing to know is that JavaScript determines its scope lexically; it looks to the source code itself and the positioning of the variables in the code to make a determination about what that variable’s scope is.
There are a few different types of scope that a variable can be a part of, so let’s take a quick, high-level pass at those. It can be helpful to think about the different scopes as a large hierarchy in which some scopes are nested underneath/inside others. We’ll go into this in a bit more detail, but let’s check on the different scopes first.
The first is the global scope. This is the top of the hierarchy, and all the other scopes that get created fall within this scope. If no other scope is created when a variable is declared, then that variable falls into the global scope.
The second scope is function scope; this is the scope that is created when a function is declared. This function creates its own local scope within the global scope in which it exists. Variables that are declared within the local scope of the function are not accessible from outside of that local scope.
A key point here is that while variables declared within the local scope of the function are not accessible to the outer scopes, the variables declared in the outer scopes are accessible inside the local scope!
The third scope is block scope; this is created by a code block, most commonly with if, for, and while statements and the like. The variables that are declared within a code block follow nearly identical rules to those for functions with one notable exception.
Variables declared with let and const fall within the local scope of the block; they can not be accessed outside of the block. Just as with function scope, the variables declared outside of the block are accessible within it.
The exception here though, is that variables declared with var are not scoped at the block level, so they are accessible outside of the block!
Hoisting
Hoisting is a simple (debatable) mental model to describe what happens during the creation phase of the JavaScript engine’s operation, prior to the execution phase. In the creation phase, the engine combs through the code and determines what scope each variable belongs to. I won’t go into the details here, rather, we’ll look at how we can think about the code would effectively look during this creation phase. It is important to note that I say ‘how the code would effectively look’ and not ‘how the code looks’; the JavaScript engine does not actually rearrange any code! These mental models are just that, human friendly models we can create to understand what is happening.
The rules for hoisting are fairly simple (debatable), so I will try to run through them quickly.
Variable declarations, function declarations, and class declarations are all hoisted (read: effectively, not actually moved) to the top of their respective scope. Function declarations are hoisted above variable declarations, which are in turn hoisted above function calls. Sound a bit confusing? Let’s check it out:
^^…would get hoisted into code that effectively reads like this:
Note how the function declaration is at the top of the scope along with the entire body of the function. Note also that while the let declaration is hoisted above the function call, the assignment of the variable is not. This behavior leads to a few interesting consequences. The first of which is that when let declarations are hoisted, the variable that is initialized is not set to a value. This means that if we tried to reference the variable at that point, an exception would be raised.
At this point it is prudent to note that there is a difference between declaring a variable without an initializer and what we see in the hoisted code. Normally, when you only declare a variable with a let statement, JavaScript assigns the value undefined to it. This is not what we see with the hoisted code.
Detours aside, there is one other interesting behavior to note about hoisting with variable declarations. When using var for declarations, the declaration is hoisted just as with let, but the variable is assigned a value of undefined instead of being unset!
Get Kyle Ledoux’s stories in your inbox
Join Medium for free to get updates from this writer.
Other notes, presented in bullet point for brevity’s sake:
- function expressions are hoisted just like variable declarations
- defining methods within non-function blocks has undefined behavior and can cause unpredictable outcomes
- each JS implementation can have a different result for the same code in this situation
There are more facets to hoisting and some interesting edge cases, but we aren’t going to comb through all of them at present.
Closures
The last concept we’ll quickly review here is that of closures. Closures are created when a function is defined; they are typically explained as the combination of a function enclosed with references to its lexical environment. Really, what this means is that there are two parts of a closure: the function itself & the collection of references to variables around and within the definition.
When a function is defined, the JavaScript engine checks the lexical scope of the function, figures out which variables are in scope for that function, packages those references into a collection, and attaches that collection to the function. An easy mental model to draw here is one of a container (envelope, backpack, box, briefcase, take your pick!) that is attached to the function. When the function is defined, JavaScript determines which variables are in scope and packs up pointers to those variables in the function’s briefcase. Then when the function is used, JavaScript knows which variables it does and does not have access to; they’re all right there in that briefcase, all it has to do is crack it open and see if there is a pointer for that variable.
It is important to remember that the collection of references that is stored in the function’s briefcase is a collection of references to the variables, not the variables themselves. This is critical for the function to be able to track changes to the variable, whether those be changes to what the variable itself references, or what the variable contains.
In this example, pet is declared in the global scope and so is the function checkPet. Inside checkPet there is a function call that references the variable pet. If JavaScript created a closure that included a reference to the value of the variable pet , checkPet would not have been able to track the reassignment on line 5. However, since the closure packages a pointer to the variable, not the value of the variable, it sees this change and responds accordingly!
Remember that closures create their references from the point of the function’s definition; it doesn’t matter if a variable is in scope when that function is called. As long as a variable is in scope for a function when it is defined, it will be able to access it when it is called.
The rules for closures are generally fairly straightforward, but there are some edge cases and different practical applications; I won’t cover them all here, but I would recommend reading up on Partial Function Applications if you are unfamiliar.
The Goods
As referenced earlier, I have created a collection of example exercises designed to test the different behaviors, edge-cases, and ‘gotchas’ relating to JavaScript’s handling of variable scope, hoisting, and closures. These examples are not all encompassing, but I feel it gives a decently comprehensive overview of the concepts. This is meant to be a supplemental review for personal formative assessment, so please treat it as such.
The examples all follow the same basic pattern:
- Example number
- Example code
- Commented answer
- Where applicable, code samples to illustrate hoisting behavior
Remember that the answer is below the example, so scroll carefully if you don’t want to reveal the answers!
If you have any questions, comments, corrections, or recipes, please send them my way!
Cheers!








