Javascript hoisting explained

Often I see problems arise when people try to tackle Javascript. Taking a certain behavior for granted can get them frustrated. Javascript has little differences but they can cause unexpected results if not taken into account. One of those differences is the  way it handles scope.

What is hoisting?

In Javascript, you can have multiple var-statements in a function. All of these statements act as if they were declared at the top of the function. Hoisting is the act of moving the declarations to the top of the function.

Hoisting versus block-scope

In most programming languages there’s something called block-scope. Basically what it means is that every variable declared within a block is only visible within that block. The following code snippet in c# shows this behavior:

// C#
void mymthod()
{

    // code
    for (int i = 0; i < 10; i++)
    {
        int myvar = 5;
    }

    // more code
    Console.WriteLine(myvar);     // this will raise a compiler error
    // myvar is not available here
    
}

In the snippet above, the variable myvar is only available inside the for-loop. That’s because c# has block-scope (just as VB.NET, Java and most other languages).

If we look at Javascript however, there’s only two scopes: global and function scope. So if we write an equivalent method in Javascript the result is quite different:

function mymethod() {

    for (var i = 0; i < 10; i++) {
        var myvar = 5;
    }

    alert(myvar); // Will alert 5
}

mymethod();

Try out the fiddle

Because Javascript “hoists” the declaration of myvar to the top of the function this will actually alert ‘5’. The above code is functionally equivalent to this, which explains the behavior:

function mymethod() {
    var myvar;
    for (var i = 0; i < 10; i++) {
        myvar = 5;
    }

    alert(myvar); // Will alert 5
}

mymethod();

Unexpected behavior due to hoisting

As said, if you expect your variables to be block-scoped, you will get unexpected behavior and unexplainable errors. Let’s look at a few examples:

Variable hoisting

var myvar = "global variable";

function mymethod(){
    alert(myvar);   // expected "global variable", result: undefined
    var myvar = "local variable";
    alert(myvar); // local variable
}

mymethod();

Try out the fiddle

Normally you would expect the first alert to display the value of the global variable since you haven’t overwritten it yet. However, since the declaration is actually hoisted to the top it overwrites it at the start of the function.
The following code is functionally equivalent:

var myvar = "global variable";

function mymethod(){
    var myvar;  // this is the same as var myvar = undefined;
    alert(myvar);   // undefined
    myvar = "local variable";
    alert(myvar); // local variable
}

mymethod();

Function hoisting

When we are declaring functions inside a function there are two patterns available: function expressions and function declarations:

function something(){  
    // function declaration
    function mymethod(){}
        
    // function expression
    var mysecondmethod = function() {};
    
    mymethod();
    mysecondmethod();
}

As you can see, both implementations are valid and you can call them using the same syntax. This might lead you to believe that they are in fact equivalent. However, there is a small difference when it comes to hoisting: function declarations get hoisted completely (name AND implementation) whereas function expressions get only the variable declaration hoisted and not the implementation. Let’s look at an example:

function mymethod(){
   alert("global mymethod");
}

function mysecondmethod(){
   alert("global mysecondmethod");
}

function hoisting(){
    alert(typeof mymethod);
    alert(typeof mysecondmethod);
    
    mymethod();         // local mymethod
    mysecondmethod(); // TypeError: undefined is not a function
    
    // mymethod AND the implementation get hoisted
    function mymethod(){
        alert("local mymethod");  
    }
    
    // Only the variable mysecondmethod get's hoisted
    var mysecondmethod = function() {
        alert("local mysecondmethod");  
    };
}
hoisting();

Try the fiddle

In this example you can see that, as with normal variables, just the presence of mymethod and mysecondmethod inside the function moves them to the top. However, mymethod (a function declaration) gets hoisted completely and therefore is available everywhere inside the function. On the other hand, mysecondmethod (a function expression) has only the var declaration hoisted, which prevents the global method from being seen but is not usable as a function until it’s assigned.

Best practices

From the examples, it’s obvious that the hoisting behavior Javascript exhibits can lead to confusing results and make it difficult to read code. Therefore you should follow some best practices.
What you really want when you read code, is that it clearly states what it does. Since you know it’s moving the variable declarations to the top, you should do this explicitly, so it’s obvious that they’re there. Consider the following examples (the first one is the “wrong” one, the second example show a better version):

 

function badhoistingpractices(){
    var myvar = getFromWhere();
      
    if (myvar == 5){
        var mysecondvar = 20;
        somefunction(mysecondvar);
    }    
}

function betterhoistingpractices(){
    var myvar, mysecondvar;
    
    myvar = getFromWhere();
      
    if (myvar == 5){
        mysecondvar = 20;
        somefunction(mysecondvar);
    }    
}

 

Sidenote

For completeness sake, I need to mention that behind the scenes things are actually implemented a little bit different. The ECMAScript standard does not define hoisting as “the declaration gets moved to the top of the function”. Handling code happens in two steps: the first one is parsing and entering the context and the second one is runtime code execution. In the first step variables, function declarations and formal parameters are created. In the second stage function expressions and unqualified identifiers (undeclared variables) are created. However for practical purposes we can adopt the concept of hoisting.

Conclusion

With this post I tried to demystify some of the strange behavior you sometimes may experience. I hope by reading this and applying the concept you can avoid some frustration in the future. Following the best practices will get you a long way, and remember: it’s Javascript, it IS different.

  • http://imadeveloper.com Jason

    Great article, well-explained.

  • http://github.com/rwldrn Rick Waldron


    The ECMAScript standard does not define hoisting at all. However from a functional point of view hoisting is a valid concept to adopt.

    Correction: it is. http://es5.github.io/x10.html#x10.5

    • Kenneth Truyers

      Hello,

      Well what I meant by that is that at the implementation level things are a little bit different. It’s not that the declaration just gets moved and that’s that. It’s just that is a valid assumption to make.

      In the section you linked you can see that behind the scenes a lot more is going on than just the move.

      • http://github.com/rwldrn/tc39-notes Rick Waldron

        Yes, I’m very familiar with all of the content at the link I provided you and yes it does specify, step-by-explicit-step, the “hoisting” behaviour that must be adhered to in a conforming implementation. Your post says “The ECMAScript standard does not define hoisting at all.” which is wrong. Your reply here claims “the implementation level things are a little bit different”, which is wrong—if this were true, it would be a bug in the implementation.

        I’m not trolling you or trying to embarrass or shame you. As one of jQuery’s representatives to Ecma/TC39, all that I care about is dissemination of factual information. Please stop blowing me off and publish a correction. Thank you

        • Kenneth Truyers

          Hello Rick,
          Thanks for your further explanation. I just want to say that I’m not trying to blow you off, nor do I think you’re trolling me. However, I still disagree with your point of view and I think my statement still stands. What I meant to say is that implementation-wise, the variable declaration just doesn’t get moved, there’s other things happening under the hood. From a user’s point of view that is not important since the behavior is the same.
          I have edited the particular section to try and explain it better. I hope you can see my point of view and the reason why I added that sidenote (and the reason why I’m sticking with it)

  • Pingback: YUI Weekly for April 26th, 2013 - YUI Blog

  • bigbossSNK

    Well written.
    You should remove this. from this.mymethod() and this.mysecondmethod() as they produce an error.

    • Kenneth Truyers

      You’re right, I fixed it. Don’t know why I did it like that. Thanks!

  • Justin

    Does this mean things like variable declarations in for loops get hoisted, too? So declaring i in a for loop once will result in it getting hoisted to the top and any subsequent call to i in a for loop under the first one (but not inside) will have the same final value as i? Does that make sense? Guess I could try it out in dev tools, but this is more fun.

    • Kenneth Truyers

      Yes, variables only have function scope, so in a loop they are shared as well:

      function something(){
      for (var i=0;i<10;i++){}
      alert(i); // will alert 10
      }

  • http://setapp.me Elad Ossadon

    var myvar;  // this is the same as var myvar = undefined;

    Not exactly, as redefining a variable which was already assigned before will not assign undefined as value. So it’s more like “if myvar is not allocated, allocate it (with undefined as a value), otherwise, ignore”

    a = 1; var a; alert(a)

  • Kenneth Truyers

    In this case it is, since it’s a hoisted variable:
    var myvar = 1;
    function something(){
    var myvar;
    alert(myvar); // will output "undefined"
    }

    My point was that if you define it inside a function that it gets hoisted and thus by default set to undefined, hiding the access to any global variables with that name you may have.

  • Pingback: Web links 21/05/2013 — Nevma Developers Blog

  • http://test.com/ a

    Super good!

  • Sid Vyas

    Can you explain this :

    var is_android = true;
    if(is_android) {
    function foo() {
    console.log(‘I am Android’);
    }
    } else {
    function foo() {
    console.log(‘I am NOT Android’);
    }
    }
    foo();

    Its really confusing.. :-(

    • MikGan

      @Sid remember “function declarations get hoisted completely (name AND implementation)” so after hoisting the function actually looks something like this:

      ===========================
      var is_android = true;

      /* hoisted upto here */
      function foo() {
      console.log(‘I am Android’);
      }

      /* hoisted upto here */
      function foo() {
      console.log(‘I am NOT Android’);
      }

      if (is_android) {
      /* hoisted out of here */
      } else {
      /* hoisted out of here */
      }

      foo();
      ===========================

      The functions have been hoisted to the top and by effectively declaring the function twice the last one wins and overwrites the implemenation of the first.

  • Md.Ibrahim

    Can you please tell me why following throws error:


    function hello(){
    console.log(x); //error here
    x = 3.14; //global variable
    console.log(x);
    }
    hello();

    • http://www.kenneth-truyers.net/ Kennethtruyers

      Because x is not hoisted. If you don’t declare x with a var-keyword, it will not be hoisted. When it reaches the second line (assuming it does) it just assigns it to a global variable. But because in the first line it tries to print something that doesn’t yet exist, it throws an error.

      To clarify this, you can rewrite your example like this:


      function hello(){
      console.log(window.x); //error here
      window.x = 3.14; //global variable
      console.log(window.x);
      }
      hello();

      Or similar, but instead of a global variable with a variable on an object. (global variables are just that, a variable on the window object, which is global):


      function hello(){
      var myObject = {};
      console.log(myObject.x); //error here
      myObject.x = 3.14;
      console.log(myObject.x);
      }
      hello();