Post cover

A Simple Explanation of Hoisting in JavaScript

Variables in a program are everywhere. They are small pieces of data and logic that interact with each other: and this activity makes the application alive.

In JavaScript, an important aspect of working with variables is hoisting, which defines when a variable is accessible. If you're looking for a detailed description of this aspect, then you're in the right place. Let's begin.

Before I go on, let me recommend something to you.

The path to becoming good at JavaScript isn't easy... but fortunately with a good teacher you can shortcut.

Take "Modern JavaScript From The Beginning 2.0" course by Brad Traversy to become proficient in JavaScript in just a few weeks. Use the coupon code DMITRI and get your 20% discount!

1. Introduction

Hoisting is the mechanism of moving the variables and functions declaration to the top of the function scope (or global scope if outside any function).

Hoisting influences the variable life cycle, which consists of 3 steps:

  • Declaration - create a new variable. E.g. let myValue
  • Initialization - initialize the variable with a value. E.g. myValue = 150
  • Usage - access and use the variable value. E.g. alert(myValue)

The process usually goes this way. First, a variable should be declared, then initialized with a value, and finally used. Let's see an example:


// Declare
let strNumber;
// Initialize
strNumber = '16';
// Use
console.log(parseInt(strNumber)); // => 16

Open the demo.

A function can be declared and later used (or invoked) in the application. The initialization is omitted. For instance:


// Declare
function sum(a, b) {
return a + b;
}
// Use
console.log(sum(5, 6)); // => 11

Open the demo.

Everything looks simple and natural when steps are successive: declare -> initialize -> use. If possible, you should apply this pattern when coding in JavaScript.

JavaScript does not enforce following strictly this sequence and offers more flexibility. For instance, functions can be used before the declaration: use -> declare.

The following code sample first calls the function double(5), and only later declares it function double(num) {...}:


// Use
console.log(double(5)); // => 10
// Declare
function double(num) {
return num * 2;
}

Open the demo.

It happens because the function declaration in JavaScript is hoisted to the top of the scope.

Hoisting affects differently:

  • variable declarations: using var, let, or const keywords
  • function declarations: using function <name>() {...} syntax
  • class declarations: using class keyword

Let's examine these differences in more detail.

2. Function scope variables: var

The variable statement creates and initializes variables inside the function scope: var myVar, myVar2 = 'Init'. By default a declared yet not initialized variable has undefined value.

Plain and simple, developers use this statement from the first JavaScript versions:


// Declare num variable
var num;
console.log(num); // => undefined
// Declare and initialize str variable
var str = 'Hello World!';
console.log(str); // => 'Hello World!'

Open the demo.

Hoisting and var

Variables declared with var are hoisted to the top of the enclosing function scope. If the variable is accessed before the declaration, it evaluates to undefined.

Suppose myVariable is accessed before declaration with var. In this situation the declaration is moved to the top of double() function scope and the variable is assigned with undefined:


function double(num) {
console.log(myVariable); // => undefined
var myVariable;
return num * 2;
}
console.log(double(3)); // => 6

Open the demo.

JavaScript moves the declaration var myVariable to the top of double() scope and interprets the code this way:


function double(num) {
var myVariable; // moved to the top
console.log(myVariable); // => undefined
return num * 2;
}
console.log(double(3)); // => 6

Open the demo.

The var syntax allows not only to declare but right away to assign an initial value: var str = 'initial value'. When the variable is hoisted, the declaration is moved to the top, but the initial value assignment remains in place:


function sum(a, b) {
console.log(myString); // => undefined
var myString = 'Hello World';
console.log(myString); // => 'Hello World'
return a + b;
}
console.log(sum(16, 10)); // => 26

Open the demo.

var myString is hoisted to the top of the scope, however the initial value assignment myString = 'Hello World' is not affected. The above code is equivalent to the following:


function sum(a, b) {
var myString; // moved to the top
console.log(myString); // => undefined
myString = 'Hello World'; // remains
console.log(myString); // => 'Hello World'
return a + b;
}
console.log(sum(16, 10)); // => 26

Open the demo.

3. Block scope variables: let

The let statement creates and initializes variables inside the block scope: let myVar, myVar2 = 'Init'. By default a declared yet not initialized variable has undefined value.

let is scoped on a block statement (and function) level:


if (true) {
// Declare name block variable
let month;
console.log(month); // => undefined
// Declare and initialize year block variable
let year = 1994;
console.log(year); // => 1994
}
// name and year or not accessible here, outside the block
console.log(year); // ReferenceError: year is not defined

Open the demo.

Hoisting and let

let variables are registered at the top of the block. But when the variable is accessed before the declaration, JavaScript throws an error: ReferenceError: <variable> is not defined.

From the declaration statement up to the beginning of the block the variable is in a temporal dead zone and cannot be accessed.

Let's follow an example:


function isTruthy(value) {
if (value) {
/**
* temporal dead zone for myVariable
*/
// Throws ReferenceError: myVariable is not defined
console.log(myVariable);
let myVariable = 'Value 2';
// end of temporary dead zone for myVariable
console.log(myVariable); // => 'Value 2'
return true;
}
return false;
}
isTruthy(1)

Open the demo.

myVariable is in a temporal dead zone from the top of the block if (value) {...} until let myVariable. If trying to access the variable in this zone, JavaScript throws a ReferenceError.

4. Constants: const

The constant statement creates and initializes constants inside the block scope: const MY_CONST = 'Value', MY_CONST2 = 'Value 2'. Take a look at this sample:


const COLOR = 'red';
console.log(COLOR); // => 'red'
const ONE = 1, HALF = 0.5;
console.log(ONE); // => 1
console.log(HALF); // => 0.5

Open the demo.

When a constant is defined, it must be initialized with a value in the same const statement. After declaration and initialization, the value of a constant cannot be modified:


const PI = 3.14;
console.log(PI); // => 3.14
PI = 2.14; // TypeError: Assignment to constant variable

Open the demo.

Hoisting and const

Constants const are registered at the top of the block.

The constants cannot be accessed before declaration because of the temporal dead zone. When accessed before the declaration, JavaScript throws an error: ReferenceError: <constant> is not defined.

const hoisting has the same behavior as the variables declared with let statement (see hoisting and let).

Let's define a constant in a function double():


function double(number) {
// temporal dead zone for TWO constant
console.log(TWO); // ReferenceError: TWO is not defined
const TWO = 2;
// end of temporal dead zone
return number * TWO;
}
double(5);

Open the demo.

If TWO is used before the declaration, JavaScript throws an error ReferenceError: TWO is not defined. So the constants should be first declared and initialized, and later accessed.

5. Function declarations

The function declaration defines a function with the provided name and parameters.
An example of a function declaration:


function isOdd(number) {
return number % 2 === 1;
}
console.log(isOdd(5)); // => true

Open the demo.

The code function isOdd(number) {...} is a declaration that defines a function. isOdd() verifies if a number is odd.

Hoisting and function declaration

Hoisting in a function declaration allows using of the function anywhere in the enclosing scope, even before the declaration. In other words, the function can be called from any place of the current or inner scopes.

The following code from the start invokes a function, but defines it afterward:


// Call the hoisted function
console.log(equal(1, '1')); // => false
// Function declaration
function equal(value1, value2) {
return value1 === value2;
}

Open the demo.

The code works nicely because equal() is created by a function declaration and hoisted to the top of the scope.

Notice the difference between a function declaration function <name>() {...} and a function expression var <name> = function() {...}. Both are used to create functions, however, have different hoisting mechanisms.

The following sample demonstrates the distinction:


// Call the hoisted function
console.log(addition(4, 7)); // => 11
// The variable is hoisted, but is undefined
console.log(minus(10, 7)); // TypeError: minus is not a function
// Function declaration
function addition(num1, num2) {
return num1 + num2;
}
// Function expression
var minus = function (num1, num2) {
return num1 - num2;
};

Open the demo.

addition is hoisted entirely and can be called before the declaration.

However minus is declared using a variable statement and is hoisted too, but has an undefined value when invoked. This scenario throws an error: TypeError: minus is not a function.

6. Class declarations

The class declaration defines a constructor function with the provided name and methods. Classes are a great addition introduced by ECMAScript 6. Classes are built on top of the JavaScript prototypal inheritance and have some additional goodies like super (to access the parent class), static (to define static methods), extends (to define a child class), and more.

Take a look at how to declare a class and instantiate an object:


class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
move(dX, dY) {
this.x += dX;
this.y += dY;
}
}
// Create an instance
const origin = new Point(0, 0);
// Call a method
origin.move(50, 100);

Hoisting and class

The classes are registered at the beginning of the block scope. But if you try to access the class before the definition, JavaScript throws ReferenceError: <name> is not defined. So the correct approach is first to declare the class and later use it to instantiate objects.

Hoisting in class declarations is similar to variables declared with let statement (see 3.).

Let's see what happens if a class is instantiated before declaration:


// Use the Company class
// Throws ReferenceError: Company is not defined
const apple = new Company('Apple');
// Class declaration
class Company {
constructor(name) {
this.name = name;
}
}
// Use correctly the Company class after declaration
const microsoft = new Company('Microsoft');

Open the demo.

As expected, executing new Company('Apple') before the class definition throws ReferenceError. This is nice because JavaScript suggests using a good approach to first declare something and then make use of it.

7. Final thoughts

Hoisting in JavaScript has many forms. Even if you know exactly how it works, the general advice is to code variables in a sequence of declare > initialize > use. This will save you from unexpected variable appearances, undefined and ReferenceError.

As an exception, sometimes functions can be invoked before the definition: an effect of function declaration hoisting. It's useful in cases when you need to read quickly how functions are invoked at the top of the source file, without scrolling down and reading the details about function implementation.

Like the post? Please share!

Dmitri Pavlutin

About Dmitri Pavlutin

Software developer and sometimes writer. My daily routine consists of (but not limited to) drinking coffee, coding, writing, overcoming boredom 😉. Living in the sunny Barcelona. 🇪🇸