Hoist your knowledge of JavaScript hoisting

Prarthana S. Sannamani
codeburst
Published in
10 min readMay 29, 2018

--

Photo by Samuel Zeller on Unsplash

In this article, we will learn about JavaScript hoisting, an important concept you need to be familiar with to avoid tricky JavaScript bugs. It is frequently tested in interviews too.

When I set out to learn hoisting, I was new to JavaScript and found it difficult to wrap my head around its unique features. Hardly any article contrasted JavaScript with traditional languages such as C++ to highlight the radical difference in the thought process. This article is my attempt to bridge that gap.

We will start with a basic overview of hoisting, review some related concepts, and learn in-depth about hoisting with several examples.

What is hoisting?

What the first thing that comes to your mind when I say hoisting? You probably visualized something being lifted/raised to a higher level.

In the same manner, JavaScript hoisting is a mechanism by which JavaScript “lifts” variables and function declarations to the top (of their scope).

There are two types of hoisting: variable hoisting and function hoisting.

Refresher on related concepts

Before we proceed to hoisting, let’s explore a few important concepts in JavaScript that will help you understand the topic better. If you have advanced JavaScript knowledge, feel free to skip this section.

1. Scope

Scope defines the region within which a variable or function is accessible.

If you are coming from a traditional language background such as C++, you are used to what is known as block level scope, that is, the scope of a variable is the block within which it is defined, where a block is everything enclosed within curly braces {}. Let’s examine a simple example:

When you run the program, you encounter the following error:

main.cpp: In function 'void testBlockScope()':
main.cpp:21:41: error: 'b' was not declared in this scope
cout << "Outside if block: b = " << b << "\n";
^

Since b is declared inside the if block, it exists only inside it due to block level scope. When we try to access b from outside the if block, it throws an error.

Meanwhile, a is declared in the testBlockScope function block. Hence, it available inside the entire function block, including nested blocks, such as the if block.

If you comment out Line 13, the program runs without any errors:

Inside if block: a = 5Inside if block: b = 10Outside if block: a = 5...Program finished with exit code 0
^

Let’s rewrite the same program in JavaScript using the keyword var to define variables and see the difference.

No error!

This is because JavaScript var keyword has function level scope, that is, a variable’s scope is the function within which it is declared, not the block within which it is declared. This is due to the fact that blocks such as if blocks do not create new scope; only functions do. This may seem like a strange concept to you if you are hard wired to see curly blocks as boxes which limit a variable’s access.

Interview Tip: EMCAScript 6 introduced the let keyword, which has block level scope in JavaScript. Therefore, pay attention to whether var or let is used to declare variables.

To increase our understanding of block level and function level scope, let’s consider the analogy of hills. Assume that global scope is the land at sea level and local scopes are hills. If you stand on top of a hill, you can see (access) variables and functions below your altitude. However, if you are sea level, you cannot see (access) variables and functions at a higher altitude.

In C++, every block {} results in the formation of a new hill (local scope), at an altitude one level higher than the one it is enclosed in. Nested blocks result in multi-level hills.

In JavaScript, only a function results in the formation of a new hill (local scope). Other blocks such as if blocks are present on the same altitude.

Therefore, if a variable is declared on a certain hill (block), it can be accessed from that hill (block) and all hills (blocks) above it.

You can observe this in the following diagrams, with a sample function magic.

Note: Although it is not shown, the magic function is also a hill (local scope) on sea level land (global scope).

C++ scope explained using the hill analogy
JavaScript scope explained using the hill analogy

Unlike C++, JavaScript allows nested functions, and an inner function (higher hill) can access an outer function’s variables/functions (lower hill), but not vice versa using the hill analogy.

2. Variable Declaration and Variable Definition

Variables are declared as below:

var a;

Variables are assigned values as below:

a = 5;

Usually, developers declare and define a variable in the same line:

var a = 5;

3. Function Declarations and Function Expressions

If you are a C++ programmer, you will be familiar with the terms “function declarations” and “function definitions”. Let us examine them to ensure you do not get confused as we move ahead with JavaScript functions.

C++ function declarations are statements that have a return type, function name and a parameter list, if any. They do NOT contain the body of the function.

C++ function definitions consist of the function declaration AND the body of the function.

In JavaScript, the terminologies differ slightly.

A JavaScript function declaration consists of the function keyword, a mandatory function name, list of parameters (if any) and the function body enclosed in curly braces{}. Notice that the function body is part of the function declaration here.

A JavaScript function expression consists of the function keyword, an optional function name, list of parameters(if any) and the function body enclosed in curly braces {}. However, the difference is that the function keyword does not appear at the start of a JavaScript statement. If you write each JavaScript statement on a new line, the layman’s explanation would be that they do not appear at the start of the line; rather, they are part of an extended expression.

Go through the following examples of JavaScript function expressions and read below for detailed explanations.

JavaScript functions are first-class objects and thus, they can be assigned to a variable or property of an object. While something similar can be achieved in C++ with function pointers (functors) and overloading operator() , this is not commonly seen in simple programs.

Notice that the function name is optional in function expressions, which categorizes them into anonymous function expressions (function does not have a name) and named function expressions.

Generally, named function expressions are preferred as they provide more helpful error messages and call stacks while debugging.

IIFE (Immediately Invoked Function Expressions) deceptively look like function declarations but observe that they start with ( , and not the function keyword. Due to the parenthesis at the end of the expression (), the function is invoked immediately with the arguments passed.

Deep Dive into JavaScript Hoisting

Now that we have reviewed the above concepts, let me a share a small story before we move on to the topic of this article, JavaScript hoisting.

C was my first language and I was drilled to follow the golden rule: “Declare variables and functions before you use them.”. I believe that we tend to think about programming largely in the first language we learnt, and naturally, the rule was so deep rooted in my mind that for years, I practiced it across several languages.

I would like to request you to stop here and think. Do you believe this rule holds in every language? If yes, it’s time to unlearn it and open your mind to something very different.

As strange as it sounds, JavaScript hoisting allows you to use a variable or functions before you declare them, subject to certain rules.

1. Variable Hoisting

The core concept in variable hoisting is that variable declarations (only declarations, and not the definitions!) are hoisted to the top of their scope before the program is executed. Scope can refer to global scope or function scope.

Even though you might declare and define a variable in a single statement, JavaScript processes them separately: declaration followed by a definition.

Let us review some simple examples. Try to guess what will be printed.

Assume the example below is in global scope.

The usual guess is:

Reference error: a is not defined
After: 10

However, the right answer is:

Before: undefined
After: 10

This is because hoisting “re-arranges” the code as below, which is what the JavaScript interpreter sees:

Note: JavaScript variables are initialized to undefined when they are declared.

What about variables in a function (local scope)? Let’s see.

Once again, the result is:

Before: undefined
After: 10

Here is the equivalent of the code, as affected by hoisting:

Let’s try another example.

You might have guessed that undefined will be printed. However, JavaScript hoists the variable declaration at the top of its scope, and due to function level scope, the scope of a is hoistExample.

Here is the equivalent of the code, as affected by hoisting:

When the console.log statement runs in the global scope, it finds no declaration of the a variable, and hence, a ReferenceError: a is not defined follows.

Even if you interchange Line 5 and 6, the ReferenceError occurs.

Let’s try one more example with global and local scope.

Now, there is no Reference Error. The result is:

Value of a in global scope: undefined
Value of a in local scope: 10

Here is the equivalent of the code, as affected by hoisting:

Let’s move on to the last example in variable hoisting. Think carefully!

If you thought this was the same as the third example, and guessed that ReferenceError: a is not defined will be printed, look again. The answer is 10. How?

Notice that Line 2 is a = 10 , and not var a = 10 . This makes a BIG difference. Variables which are assigned without a var declaration are considered to be global variables!

Here is the equivalent of the code, as affected by hoisting:

If you interchange Lines 7 and 8, you will get a ReferenceError: a is not defined as the interpreter has not executed hoistExample yet and thus, the global variable hasn’t been created yet.

Interview Tip: Pay attention to whether variables are declared without var in functions.

2. Function Hoisting

The rules of function hoisting are:

  • Function declarations are hoisted to the top of their enclosing scope. Remember that JavaScript function declarations contain the body of the function.
  • Function expressions are not hoisted. However, if they are assigned to a variable, JavaScript follows variable hoisting and hoists the variable declaration, but not the assignment, that is, the function.

Let us review some examples.

Do you think the function can be called? Yes! The console displays I am being hoisted.

Observe the code that the interpreter sees due to hoisting:

Let us try to understand the second example.

funcHoist is a function expression, so if you follow the rules above, it doesn’t get hoisted. But what happens when we run it?

TypeError: testFunc is not a function

Here is the equivalent of the code, as affected by hoisting:

When Line 3 is executed, testFunc is a variable with the value of undefined. Hence, we see a TypeError as it cannot be executed.

Here is an example with nested functions:

What do you think gets returned?

The answer is 10.

Here is the equivalent of the code, as affected by hoisting:

The innerFuncHoist function declaration gets hoisted to the top of its scope, which is funcHoist.

It gets more interesting. Let’s consider an example with multiple function declarations for the same function:

50 is returned. Here is the code that the interpreter sees:

As you can see above, both function declarations of testHoist get hoisted. The second time the interpreter encounters testHoist , it gets redefined to return a value of 50. Hence, when testHoist is invoked, 50 is returned.

Let’s wrap this up by considering one last example:

100 is returned. Look carefully at the code that the interpreter sees:

Note: If the function invocation test() was at Line 1 instead of Line 5 in jsFuncHoistBefore5.js (and the rest of the code shifted down by one line), what would be the output?

The answer is TypeError: test is not a function . When the interpreter executes test() (now on Line 3 in jsFuncHoistAfter5.js), the test var would still be undefined.

Cheat sheet: How to approach a hoisting problem

  1. Separate all variable assignments into a variable declaration statement and an assignment statement.
  2. Determine the scope of variable declarations and function declarations in the code. It can be global scope or function scope. If it helps, draw arrows from the statement to the top of their enclosing scope.
  3. Place all the variable declarations at the top of their enclosing scope. (This can include variables which have functions assigned to them). Leave behind the variable assignments in their original positions.
  4. Gather the function declarations and place them below the variable declarations in their scope.
  5. Consider the code in the top-down direction and “mock” execute it.
  6. Handle re-declarations and re-assignments of variables and functions carefully by considering the order in which they are performed. Ensure that they have the intended value.

I hope this article increased your understanding of JavaScript hoisting, and provided value to your time. Thank you for reading! I will appreciate any feedback.

Follow me on Twitter here and LinkedIn here.

✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst on Twitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.

--

--