Scoping & Hoisting in JavaScript

Prerequisite : Understanding Javascript Scoping, Javascript Hoisting

Do you know what value will be printed on the console when the following piece of code will be executed?

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
function test()
{
    var x = 20;
}
  
test();
console.log(x);

chevron_right


If your answer is 10, then you are right. Here the variable ‘x’ declared (and of course initialized) outside the function ‘test’ has a global scope and that’s why it is accessible anywhere in this scope globally. However, the one declared and initialized inside the ‘test’ function can be accessible only inside that function. So the below code snippet will print 20 on the console upon execution.

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
  
function test()
{
    var x = 20;
    console.log(x);
}
  
test();

chevron_right


Now try to guess the output for this one.

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
  
function test()
{
    if (x > 20) {
        var x = 50;
    }
  
    console.log(x);
}
  
test();

chevron_right


If you have guessed 10 following the scoping logic discussed in the previous examples, then you are unfortunate as 10 is not the correct answer. This time it will print ‘undefined’ on the console. This is because of the combined effect of variable scoping and variable hoisting.



Scoping in JavaScript

Let us first understand scoping. Scope is a region of the program where a variable can be accessed. In other words, scope determines the accessibility/visibility of a variable. Since JavaScript looks like a C-family language, it is very obvious to think that scoping in JavaScript is similar to that in most of the back-end programming languages like C, C++ or Java.

Let’s consider the following code snippet written in C:

filter_none

edit
close

play_arrow

link
brightness_4
code

#include<stdio.h>
void doSomething();
  
int main() {
  
    doSomething();
    return 0;
  
}
  
// This function examplifies
// the block-level-scope in C language
  
void doSomething() {
  
    int x = 10;
    printf("%d\n", x);
  
    if (x == 10) {
        int x = 20;
        printf("%d\n", x);    
    }
  
    printf("%d\n", x);
  
}

chevron_right


Output :

10
20
10

Here the output is so, because C, as well as the rest of the C-family, has block-level-scope. Whenever the control enters into a block, such as if-block or a loop (e.g., for, while or do-while), the language allows new variables to be declared that can be visible within that block only. Here the variables declared in the inner scope do not affect the value of the variables declared in the outer scope. But this is not the case in JavaScript.

Let’s consider the following JavaScript code :

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
console.log(x);
  
if (true) {
    var x = 20;
    console.log(x);
}
  
console.log(x);

chevron_right


Output :

10
20
20

Unlike the previous code, written in C, here the last output changes depending on the value assigned to the variable ‘x’ inside the if-block. This is because JavaScript does not have block-level-scope. It has function-level-scope. That means blocks such as if-statements and loops do not create new scopes in JavaScript. Rather a new scope is created only when a function is defined. This sometimes creates confusion for developers who are working with C, C++, C# or Java-like languages. Luckily, JavaScript allows function definitions to go inside any block. For example, we can write the above code by implementing an IIFE (Immediately Invoked Function Expression) inside the if-block that will result in a different output.

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
console.log(x);
  
if (true) {
    (function() {
        var x = 20;
        console.log(x);
    })();
}
  
console.log(x);

chevron_right


Output :

10
20
10

In this case, when the control enters into the anonymous function defined and invoked inside the if-block, it creates a new scope. The variable ‘x’ declared inside this scope does not affect the value of the variable ‘x’ declared in the outer scope. Though this is a quite flexible way for creating temporary scopes wherever required, it is not qualified as a good coding style. Hence, to keep the things simple ES6 has introduced two new keywords- ‘let’ and ‘const’, to declare block-scoped variables. When a variable is declared using ‘const’ or ‘let’, it is visible only inside the particular block in which it is declared. For example:



filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
console.log(x);
  
function test()
{
    var x = 20;
    console.log(x);
  
    if (x > 10) {
        let x = 30;
        console.log(x);
    }
  
    console.log(x);
}
  
test();
console.log(x);

chevron_right


Output :

10
20
30
20
10

In this case, when the test() function is invoked the console.log() statement written inside the if-block prints 30 on the console, whereas the one that follows the if-block inside test() function prints 20 on the console. This means that the variable ‘x’ declared and defined using let keyword has no effect on the value of the variable ‘x’ declared outside its scope, i.e. the if-block. The keyword ‘const’ also operates in a similar manner. The only difference between ‘let’ and ‘const’ is- const is a signal that the identifier won’t be reassigned (The reason that the word ‘identifier’ is preferred here over ‘variable’ is since ‘const’ is used to declare the identifier, it is no longer a variable. It is a constant.), whereas let is a signal that the variable may be reassigned.

Now let’s go back to the following example provided previously in this article.

filter_none

edit
close

play_arrow

link
brightness_4
code

var x = 10;
  
function test()
{
    if (x > 20) {
        var x = 50;
    }
  
    console.log(x);
}
  
test();

chevron_right


Output :

undefined

We have already discussed why in this example the console.log() statement did not print 10 on the console. Since JavaScript (especially the var keyword) has function-level-scope, the variable ‘x’ declared inside the if-block is visible throughout the function test(). So when the console.log() statement executes, it tries to print the value of the inner ‘x’ rather than the value of the one declared outside the function definition. Now the question is if this code snippet prints the value of the inner ‘x’ then why is it printing ‘undefined’? Here the inner ‘x’ is both declared and defined inside the if-block which is evaluated to false and in JavaScript an attempt to access a variable before its declaration results in a ReferenceError. Then how come the variable is even getting declared inside the function allowing it to be executed without any error? Does the function-level-scope make the conditional statements, like if-else, ineffective and if so, why is not it printing 50, the actual value of ‘x’ declared inside the function? All these questions have a single answer- hoisting.

Hoisting in Javascript

Hoisting is JavaScript’s default behavior of moving declarations to the top of their containing scope. When a JavaScript code is interpreted, the interpreter invisibly moves (hoist) all the variable and function declarations to the top of the scope they are declared in. However, the location of their definition/initialization/instantiation remains unaffected. For example, the above code snippet will be interpreted as the following before execution.

filter_none

edit
close

play_arrow

link
brightness_4
code

var x;
x = 10;
  
function test()
{
    var x;
    if (x > 20) {
        x = 50;
    }
  
    console.log(x);
}
  
test();

chevron_right


In this case, since no value is assigned to the variable ‘x’ declared at the top of the test() function, JavaScript automatically assigns the value ‘undefined’ to it. Since the if-condition is evaluated to false, the ‘console.log()’ statement prints ‘undefined’ on the console.

Now let’s look at another example:

filter_none

edit
close

play_arrow

link
brightness_4
code

function test()
{
    if (false) {
        var x = 50;
    }
  
    console.log(x);
    console.log(y);
  
    var y = 100;
    console.log(y);
}
  
test();

chevron_right


Output:

undefined
undefined
100

This code is interpreted as the following.



filter_none

edit
close

play_arrow

link
brightness_4
code

function test()
{
    var x, y;
  
    if (false) {
        x = 50;
    }
  
    console.log(x);
    console.log(y);
  
    y = 100;
    console.log(y);
}
  
test();

chevron_right


In the first line of the function body where ‘x’ and ‘y’ are declared in the interpreted code, JavaScript assigns ‘undefined’ to both the variables. Since the if-condition is evaluated to false, the first two ‘console.log()’ statements print ‘undefined’ on the console. However, the last statement prints 100 as it is assigned to ‘y’ before the final ‘console.log()’ executes.

Now the important point here is, this variable hoisting mechanism works only for variables declared using var keyword. It does not work for variables or identifiers declared using let and const keywords respectively. Let’s consider the following example.

filter_none

edit
close

play_arrow

link
brightness_4
code

function test()
{
    if (false) {
        let x = 50;
    }
  
    console.log(x);
    console.log(y);
  
    let y = 100;
    console.log(y);
}
  
test();

chevron_right


Output:

ReferenceError: x is not defined

Identifiers declared using let or const are not at all hoisted. This makes them inaccessible before their declaration in the original source code.

Now a question may come- what is the best practice to declare variables and constants in JavaScript keeping variable hoisting in mind, so that it will never make our code return an unexpected result?

Here is the answer.

  • All variables and constants that need to be visible within the scope of a particular function should be declared using ‘var’ and ‘const’ keywords respectively at the top of that function.
  • Inside blocks (conditional statements or loops) variables and constants should be declared on the top of the block using ‘let’ and ‘const’ respectively.
  • If in a particular scope multiple variables or constants need to be declared, then declare them in one go by using a single ‘var’ or ‘let’ or ‘const’ keyword with comma separated identifier names,
    e.g.,
    var x, y, z; // declaring function-scoped variables
    let a, b, c; // declaring block-scoped variables
    const u, v, w; // declaring block-scoped constants

Though the last point has nothing to do with the consequences of variable hoisting, it is a better practice to keep it in mind while writing code to keep the code clean.

Like variables, functions are also hoisted in JavaScript. However, how the hoisting will be done depends on the way a function is declared. JavaScript allows developers to define a function in two ways- function declaration and function expression.

Let’s consider the following example.

filter_none

edit
close

play_arrow

link
brightness_4
code

function test()
{
    foo();
    bar();
  
    // Function defiened
    // using function declaration
    function foo()
    {
        console.log('foo');
    }
  
    // Function defined
    // using function expression
    var bar = function() {
        console.log('bar');
    }
}
  
test();

chevron_right


Output:

foo
TypeError: bar is not a function

The JavaScript interpreter interprets the above code as follows.

filter_none

edit
close

play_arrow

link
brightness_4
code

function test()
{
    function foo() {}
    var bar;
  
    foo();
    bar();
  
    // Function defiened
    // using function declaration
    function foo()
    {
        console.log('foo');
    }
  
    // Function defined
    // using function expression
    bar = function() {
        console.log('bar');
    }
}
  
test();

chevron_right


Since function foo() is defined using function declaration, JavaScript interpreter moves its declaration to the top of its container scope, i.e., the body of test(), leaving its definition part behind. The definition is dynamically assigned when foo() is invoked. This leads to the execution of the body of foo() function when the function is called. On the other hand, defining a function using function expression is nothing other than variable initialization where the function is treated as the value to be assigned to the variable. Therefore it follows the same hoisting rule which is applicable to variable declarations. Here the declaration of the variable ‘bar’ is moved to the top of its container scope while the place of assigning the function to it remains unchanged. JavaScript cannot interpret a variable as a function until it is assigned a value which is actually a function. This is why trying to execute the statement bar(); before ‘bar’ is defined results in a TypeError.

Though the decision regarding which way one should use to define a function solely depends on developer’s own choice, it is better to keep the following things in mind.

  • Define all the functions using function declaration method on the top of their container scope. This not only keeps the code clean but also ensures that all the functions are declared and defined before they are invoked.
  • If you must have to define a function using function expression method, then make sure that it is defined before it is invoked in the code. This will eliminate the chance of unexpected outputs or errors that may result due to hoisting.


My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.




Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.