Tutorial

Understanding Hoisting in JavaScript

Updated on September 15, 2020
    Default avatar

    By Mabishi Wakio

    Understanding Hoisting in JavaScript

    While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

    Introduction

    In this tutorial, we’ll investigate how the famed hoisting mechanism occurs in JavaScript. Before we dive in, let’s get to grips with what hoisting is.

    Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

    Inevitably, this means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.

    Of note however, is the fact that the hoisting mechanism only moves the declaration. The assignments are left in place.

    If you’ve ever wondered why you were able to call functions before you wrote them in your code, then read on!

    undefined vs ReferenceError

    Before we begin in earnest, let’s deliberate on a few things.

    console.log(typeof variable); // Output: undefined
    

    This brings us to our first point of note:

    In JavaScript, an undeclared variable is assigned the value undefined at execution and is also of type undefined.

    Our second point is:

    console.log(variable); // Output: ReferenceError: variable is not defined
    

    In JavaScript, a ReferenceError is thrown when trying to access a previously undeclared variable.

    The behaviour of JavaScript when handling variables becomes nuanced because of hoisting. We’ll look at this in depth in subsequent sections.

    Hoisting variables

    The following is the JavaScript lifecycle and indicative of the sequence in which variable declaration and initialisation occurs.

    variable-hoisting

    However, since JavaScript allows us to both declare and initialise our variables simultaneously, this is the most used pattern:

    var a = 100;
    

    It is however important to remember that in the background, JavaScript is religiously declaring then initialising our variables.

    As we mentioned before, all variable and function declarations are hoisted to the top of their scope. I should also add that variable declarations are processed before any code is executed.

    However, in contrast, undeclared variables do not exist until code assigning them is executed. Therefore, assigning a value to an undeclared variable implicitly creates it as a global variable when the assignment is executed. This means that, all undeclared variables are global variables.

    To demonstrate this behaviour, have a look at the following:

    function hoist() {
      a = 20;
      var b = 100;
    }
    
    hoist();
    
    console.log(a); 
    /* 
    Accessible as a global variable outside hoist() function
    Output: 20
    */
    
    console.log(b); 
    /*
    Since it was declared, it is confined to the hoist() function scope.
    We can't print it out outside the confines of the hoist() function.
    Output: ReferenceError: b is not defined
    */
    

    Since this is one of the eccentricities of how JavaScript handles variables, it is recommended to always declare variables regardless of whether they are in a function or global scope. This clearly delineates how the interpreter should handle them at run time.

    ES5

    var

    The scope of a variable declared with the keyword var is its current execution context. This is either the enclosing function or for variables declared outside any function, global. Let’s look at a few examples to identify what this means:

    global variables

    console.log(hoist); // Output: undefined
    
    var hoist = 'The variable has been hoisted.';
    
    

    We expected the result of the log to be: ReferenceError: hoist is not defined, but instead, its output is undefined.

    Why has this happened?

    This discovery brings us closer to wrangling our prey.

    JavaScript has hoisted the variable declaration. This is what the code above looks like to the interpreter:

    var hoist;
    
    console.log(hoist); // Output: undefined
    hoist = 'The variable has been hoisted.';
    

    Because of this, we can use variables before we declare them. However, we have to be careful because the hoisted variable is initialised with a value of undefined. The best option would be to declare and initialise our variable before use.

    Function scoped variables

    As we’ve seen above, variables within a global scope are hoisted to the top of the scope. Next, let’s look at how function scoped variables are hoisted.

    function hoist() {
      console.log(message);
      var message='Hoisting is all the rage!'
    }
    
    hoist();
    

    Take an educated guess as to what our output might be.

    If you guessed, undefined you’re right. If you didn’t, worry not, we’ll soon get to the bottom of this.

    This is how the interpreter views the above code:

    function hoist() {
      var message;
      console.log(message);
      message='Hoisting is all the rage!'
    }
    
    hoist(); // Ouput: undefined
    

    The variable declaration, var message whose scope is the function hoist(), is hoisted to the top of the function.

    To avoid this pitfall, we would make sure to declare and initialise the variable before we use it:

    function hoist() {
      var message='Hoisting is all the rage!'
      return (message);
    }
    
    hoist(); // Ouput: Hoisting is all the rage!
    

    Strict Mode

    Thanks to a utility of the es5 version of JavaScript known as strict-mode, we can be more careful about how we declare our variables. By enabling strict mode, we opt into a restricted variant of JavaScript that will not tolerate the usage of variables before they are declared.

    Running our code in strict mode:

    1. Eliminates some silent JavaScript errors by changing them to explicit throw errors which will be spit out by the interpreter.
    2. Fixes mistakes that make it difficult for JavaScript engines to perform optimisations.
    3. Prohibits some syntax likely to be defined in future versions of JavaScript.

    We enable strict mode by prefacing our file or function with

    'use strict';
    
    // OR
    "use strict";
    

    Let’s test it out.

    'use strict';
    
    console.log(hoist); // Output: ReferenceError: hoist is not defined
    hoist = 'Hoisted'; 
    

    We can see that instead of assuming that we missed out on declaring our variable, use strict has stopped us in our tracks by explicitly throwing a Reference error. Try it out without use strict and see what happens.

    Strict mode behaves differently in different browsers however, so it’s advisable to perform feature testing thoroughly before relying on it in production.

    ES6

    ECMAScript 6, ECMAScript 2015 also known as ES6 is the latest version of the ECMAScript standard, as the writing of this article, Jan 2017 and introduces a few changes to es5.

    Of interest to us is how changes in the standard affect the declaration and initialisation of JavaScript variables.

    let

    Before we start, to be noted is the fact that variables declared with the keyword let are block scoped and not function scoped. That’s significant, but it shouldn’t trouble us here. Briefly, however, it just means that the variable’s scope is bound to the block in which it is declared and not the function in which it is declared.

    Let’s start by looking at the let keyword’s behaviour.

    console.log(hoist); // Output: ReferenceError: hoist is not defined ...
    let hoist = 'The variable has been hoisted.';
    

    Like before, for the var keyword, we expect the output of the log to be undefined. However, since the es6 let doesn’t take kindly on us using undeclared variables, the interpreter explicitly spits out a Reference error.

    This ensures that we always declare our variables first.

    However, we still have to be careful here. An implementation like the following will result in an ouput of undefined instead of a Reference error.

    let hoist;
    
    console.log(hoist); // Output: undefined
    hoist = 'Hoisted'
    

    Hence, to err on the side of caution, we should declare then assign our variables to a value before using them.

    const

    The const keyword was introduced in es6 to allow immutable variables. That is, variables whose value cannot be modified once assigned.

    With const, just as with let, the variable is hoisted to the top of the block.

    Let’s see what happens if we try to reassign the value attached to a const variable.

    const PI = 3.142;
    
    PI = 22/7; // Let's reassign the value of PI
    
    console.log(PI); // Output: TypeError: Assignment to constant variable.
    

    How does const alter variable declaration? Let’s take a look.

    console.log(hoist); // Output: ReferenceError: hoist is not defined
    const hoist = 'The variable has been hoisted.';
    

    Much like the let keyword, instead of silently exiting with an undefined, the interpreter saves us by explicitly throwing a Reference error.

    The same occurs when using const within functions.

    function getCircumference(radius) {
      console.log(circumference)
      circumference = PI*radius*2;
      const PI = 22/7;
    }
    
    getCircumference(2) // ReferenceError: circumference is not defined
    

    With const , es6 goes further. The interpreter throws an error if we use a constant before declaring and initialising it.

    Our linter is also quick to inform us of this felony:

    PI was used before it was declared, which is illegal for const variables.
    

    Globally,

    
    const PI;
    console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
    PI=3.142;
    

    Therefore, a constant variable must be both declared and initialised before use.


    As a prologue to this section, it’s important to note that indeed, JavaScript hoists variables declared with es6 let and const. The difference in this case is how it initialises them. Variables declared with let and const remain uninitialised at the beginning of execution whilst variables declared with var are initialised with a value of undefined.

    Hoisting functions

    JavaScript functions can be loosely classified as the following:

    1. Function declarations
    2. Function expressions

    We’ll investigate how hoisting is affected by both function types.

    Function declarations

    These are of the following form and are hoisted completely to the top. Now, we can understand why JavaScript enable us to invoke a function seemingly before declaring it.

    hoisted(); // Output: "This function has been hoisted."
    
    function hoisted() {
      console.log('This function has been hoisted.');
    };
    

    Function expressions

    Function expressions, however are not hoisted.

    expression(); //Output: "TypeError: expression is not a function
    
    var expression = function() {
      console.log('Will this work?');
    };
    

    Let’s try the combination of a function declaration and expression.

    expression(); // Ouput: TypeError: expression is not a function
    
    var expression = function hoisting() {
      console.log('Will this work?');
    };
    

    As we can see above, the variable declaration var expression is hoisted but it’s assignment to a function is not. Therefore, the intepreter throws a TypeError since it sees expression as a variable and not a function.

    Order of precedence

    It’s important to keep a few things in mind when declaring JavaScript functions and variables.

    1. Variable assignment takes precedence over function declaration
    2. Function declarations take precedence over variable declarations

    Function declarations are hoisted over variable declarations but not over variable assignments.

    Let’s take a look at what implications this behaviour has.

    Variable assignment over function declaration

    var double = 22;
    
    function double(num) {
      return (num*2);
    }
    
    console.log(typeof double); // Output: number
    

    Function declarations over variable declarations

    var double;
    
    function double(num) {
      return (num*2);
    }
    
    console.log(typeof double); // Output: function
    

    Even if we reversed the position of the declarations, the JavaScript interpreter would still consider double a function.

    Hoisting classes

    JavaScript classes too can be loosely classified either as:

    1. Class declarations
    2. Class expressions

    Class declarations

    Much like their function counterparts, JavaScript class declarations are hoisted. However, they remain uninitialised until evaluation. This effectively means that you have to declare a class before you can use it.

    
    var Frodo = new Hobbit();
    Frodo.height = 100;
    Frodo.weight = 300;
    console.log(Frodo); // Output: ReferenceError: Hobbit is not defined
    
    class Hobbit {
      constructor(height, weight) {
        this.height = height;
        this.weight = weight;
      }
    }
    

    I’m sure you’ve noticed that instead of getting an undefined we get a Reference error. That evidence lends claim to our position that class declarations are hoisted.

    If you’re paying attention to your linter, it supplies us with a handy tip.

    Hobbit was used before it is declared, which is illegal for class variables
    

    So, as far as class declarations go, to access the class declaration, you have to declare first.

    class Hobbit {
      constructor(height, weight) {
        this.height = height;
        this.weight = weight;
      }
    }
    
    var Frodo = new Hobbit();
    Frodo.height = 100;
    Frodo.weight = 300;
    console.log(Frodo); // Output: { height: 100, weight: 300 }
    

    Class expressions

    Much like their function counterparts, class expressions are not hoisted.

    Here’s an example with the un-named or anonymous variant of the class expression.

    var Square = new Polygon();
    Square.height = 10;
    Square.width = 10;
    console.log(Square); // Output: TypeError: Polygon is not a constructor
    
    var Polygon = class {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    };
    

    Here’s an example with a named class expression.

    var Square = new Polygon();
    Square.height = 10;
    Square.width = 10;
    console.log(Square); // Output: TypeError: Polygon is not a constructor
    
    
    var Polygon = class Polygon {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    };
    
    

    The correct way to do it is like this:

    var Polygon = class Polygon {
      constructor(height, width) {
        this.height = height;
        this.width = width;
      }
    };
    
    var Square = new Polygon();
    Square.height = 10;
    Square.width = 10;
    console.log(Square);
    

    Caveat

    There’s a bit of an argument to be made as to whether Javascript es6 let, const variables and classes are actually hoisted, roughly hoisted or not hoisted. Some argue that they are actually hoisted but uninitialised whilst some argue that they are not hoisted at all.

    Conclusion

    Let’s summarise what we’ve learned so far:

    1. While using es5 var, trying to use undeclared variables will lead to the variable being assigned a value of undefined upon hoisting.
    2. While using es6 let and const, using undeclared variables will lead to a Reference Error because the variable remains uninitialised at execution.

    Therefore,

    1. We should make it a habit to declare and initialise JavaScript variables before use.
    2. Using strict mode in JavaScript es5 can help expose undeclared variables.

    I hope this article will serve as a good introduction to the concept of hoisting in JavaScript and spur your interest regarding the subtleties of the JavaScript language.

    Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

    Learn more about us


    About the authors
    Default avatar
    Mabishi Wakio

    author

    Still looking for an answer?

    Ask a questionSearch for more help

    Was this helpful?
     
    4 Comments
    

    This textbox defaults to using Markdown to format your answer.

    You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

    Hello! Very good article.
    I don’t understand why the author said

    With const, just as with let, the variable is hoisted to the top of the block.

    Are const and let hoisted?

    “Conceptually, for example, a strict definition of hoisting suggests that variable and function declarations are physically moved to the top of your code, but this is not in fact what happens. Instead, the variable and function declarations are put into memory during the compile phase, but stay exactly where you typed them in your code.” MDN source: https://developer.mozilla.org/en-US/docs/Glossary/Hoisting

    Amazing explanation of all concepts! I enjoyed it very much and understood so much more.

    This gave me a thorough understanding of what Hoisting is. Thanks a lot!

    Try DigitalOcean for free

    Click below to sign up and get $200 of credit to try our products over 60 days!

    Sign up

    Join the Tech Talk
    Success! Thank you! Please check your email for further details.

    Please complete your information!

    Get our biweekly newsletter

    Sign up for Infrastructure as a Newsletter.

    Hollie's Hub for Good

    Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

    Become a contributor

    Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

    Welcome to the developer cloud

    DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

    Learn more
    DigitalOcean Cloud Control Panel