Mastering Function Hoisting in JavaScript: A 2023 Guide

Mastering Function Hoisting in JavaScript: A 2023 Guide

JavaScript remains one of the most in-demand and widely used programming languages. As per the 2022 StackOverflow Developer Survey, JavaScript is the #1 most popular language globally for the 10th year running:

Most Popular Programming Languages % Developers Using
JavaScript 69.7%
Python 44.1%
SQL 43.2%
Java 34.2%
TypeScript 33.6%

With its versatile uses on the frontend, backend, and elsewhere, developers looking to master JavaScript are highly sought after.

However, JavaScript also contains certain unique aspects like hoisting which trip up many programmers initially.

In this comprehensive guide, we will deep dive into:

  • What is hoisting
  • Function vs variable hoisting
  • Function declarations and expressions
  • Core concepts of function hoisting
  • Practical applications and impacts
  • Common interview questions with answers
  • Additional tips from a full stack developer

Properly understanding function hoisting in JS opens the doors to writing cleaner code and grasping key concepts like scope chains and execution contexts.

Let‘s get right to it!

What is Hoisting in JavaScript?

But first – what is hoisting?

Hoisting refers to an internal step in the JavaScript compilation phase where declarations are moved to the top of their enclosing scope, regardless of where they are declared in actual code.

This means variables and functions can be accessed in code before they are declared – very counterintuitive for most developers first learning JavaScript.

myFunc(); // Works! But how??

function myFunc() {
  // Function body
}

So in the example above, myFunc() is called before the actual function declaration in the code.

This works because of hoisting, which enables access to myFunc before its definition.

There are two types of hoisting:

  1. Variable hoisting – only hoists declarations, values are undefined
  2. Function hoisting – hoists the full function declaration and definition

We will focus specifically on #2, and function hoisting examples.

Key Differences: Function Declarations vs Expressions

Before diving further into function hoisting, we must first understand the key differences between function declarations and function expressions in JavaScript:

Feature Function Declarations Function Expressions
Definition Uses function keyword directly Function stored in a variable
Hoisting Fully hoisted – declaration + definition Only variable declaration hoisted
Accessibility Can call before declared in code Cannot call before variable initialization

Let‘s visualize some examples to see declarations versus expressions in code:

Function Declaration

function myFunc() {
  console.log(‘Hello there!‘); 
}

Function Expression

const myFunc = function() {
  console.log(‘Hello there!‘);   
};

The key syntactical difference comes down to whether the function name directly uses the function keyword versus being stored inside a variable.

However, the more significant impact comes from how declarations vs expressions behave with hoisting. This is critical to understand moving forward.

Function declarations can be called reliably before they appear in code due to full hoisting:

myFunc(); // Works!

function myFunc() {
  // ...
} 

Meanwhile function expressions stored in variables cannot:

myFunc(); // Fails with TypeError! 

const myFunc = function() {
   // ...  
};

The variable is hoisted, but the actual function definition isn‘t. This attempts to call undefined, throwing an error.

So why exactly do they behave differently?

The Technical Root of Function Hoisting

To really understand why function declarations are fully hoisted, we have to dive deeper into some internal JavaScript execution concepts.

Specifically – the creation of the Execution Context.

Each time JavaScript code is run, an Execution Context is created internally by the JS engine. As part of this, two phases occur:

  1. Creation Phase
    • Sets up memory for variables and functions
    • Determines scope
    • Hoisting occurs here
  2. Execution Phase
    • Code is executed statement-by-statement

With function declarations, the full body is loaded in the Creation Phase so it is accessible early.

However variable assignments happen in the Execution Phase, so function expressions are not initialized yet.

Knowing this, we can see why declarations get special treatment over expressions or statements with hoisting.

The key takeaway is that the execution flow itself enables early access to declarations over other types of code.

Next, we‘ll see some more concrete examples of this unique hoisting behavior in action.

Function Hoisting Examples

Let‘s visualize function hoisting behavior with a few examples contrasting declarations and expressions.

Declaration

hoistedExample(); // Logs "I‘m hoisted!"

function hoistedExample() {
  console.log(‘I\‘m hoisted!‘); 
} 

Even though hoistedExample() is called first, it successfully runs due to the function declaration being hoisted up during compilation.

Expression

notHoisted(); // TypeError! 

const notHoisted = function() {
  console.log(‘I am NOT hoisted!‘);
};

Here we try to invoke notHoisted before it is assigned the function in the expression – this fails.

The variable notHoisted itself is hoisted to the Creation Phase, but the value remains undefined until the execution reaches defining the function.

Hence the TypeError when trying to execute early.

Rules of Function Hoisting

Given the above examples and technical details, we can summarize behavior into three key rules:

1. Function Declarations

  • Hoisted completely to top of enclosing scope
  • Both name declaration AND actual definition
  • Can reliably call before defined in code

2. Function Expressions

  • Only the variable is hoisted, no definition
  • Cannot invoke before variable is initialized
  • Attempting so results in TypeError

3. Avoid Declarations in Blocks

  • Declaring functions in blocks like if/else can cause confusing and unintuitive behavior

These core rules form the basis of reliably using function hoisting without encountering issues in JavaScript.

Now that we have a solid grasp of the underlying concepts, next we‘ll cover some real-world applications and use cases demonstrated through examples.

Practical Applications of Function Hoisting

Properly leveraging function hoisting allows declaring functions anywhere while reliably calling them before definitions.

This grants flexibility in arranging code for both readability and maintainability.

Some major applications are:

1. Setup Helper Functions

Hoist helpers to top that are called later:

function startApp() {
  // Setup logic  
}

startApp();

function startApp() {
  // Function body
}

Keeps initialization logic separate from handlers.

2. Math Utilities

Group util functions before app logic:

// Math utilities
function sum(x, y) {
  return x + y;
}

function div(x, y) {
  return x / y; 
}

// Core logic
div(sum(2, 2), 2); 

Math utilities can be shared across files as well.

3. Web Template Helpers

Hoist templating functions clean HTML rendering:

function header(title) {
  return `
    <header>

    </header>
  `; 
}

function content(text) {
  return `<main>${text}</main>`; 
}

document.body.innerHTML = 
  header(‘Hello!‘) + 
  content(‘This is my page‘);

Much cleaner than declaring inside rendering logic itself.

The key benefit across these examples is readability – function hoisting allows us to conceptualize code execution as almost topological ordering rather than strictly line-by-line, visualizing execution flow based on dependencies rather than vertical position.

This enables writing cleaner, more maintainable code across use cases by declaring functions anywhere while reliably calling everywhere else.

Function Hoisting Interview Questions

Given its deceptive intricacies, function hoisting in JavaScript is very commonly tested during technical interviews.

Let‘s explore some examples of common hoisting questions that combine both function and variable concepts:

Interview Question 1

var x = 1;

function hoist() {
  x = 10;
  var x = 20;

  console.log(x);
}

hoist();
console.log(x); 

What gets logged out?

Answer:

10 and 1

The key thing here is there are two separate x variables in the global and function scope due to hoisting.

Inside hoist():

  1. Var x declaration hoisted -> undefined
  2. x = 10 assigns global x to 10
  3. var x = 20 declares local x, initializing it to 20
  4. Logs modified local x value of 10

The global x remains unmodified at 1.

Interview Question 2

function printNums() {
  console.log(1);

  setTimeout(function() { 
    console.log(2);
  }, 1000);  

  setTimeout(function() {
    console.log(3); 
  }, 0);

  console.log(4);
}

printNums();

What order will numbers be logged out?

Answer:

1, 4, 3, 2

The key thing here is understanding function expressions set in setTimeout callbacks are NOT hoisted. Only their declarations are.

Whereas the synchronous console.log() statements maintain sequence order.

This illustrates differences between synchronous and asynchronous flows.

Interview Question 3

function myFunction() {
  this.greeting = ‘Hello‘;
  this.sayHi = function() {
      return `${this.greeting} World!`  
  }
}
myFunction.prototype.sayHi = function() {
  return ‘Hello from prototype!‘; 
}

let myObj = new myFunction();
myObj.sayHi(); 

What gets returned?

Answer:

Hello World!

Function declarations inside the constructor override prototypes. The lower sayHi() method gets hoisted over the prototype one.

An easy precedence order mistake to make!

Additional Tips from a Full Stack Developer

Drawing from my experiences both using and teaching JavaScript across production applications, here are some further best practices around function hoisting:

Tip 1: Visually Distinguish Declarations

Mark hoisted declarations clearly for easier reading:

// === Function Hoisting ===
function hoistedFunc() {
  // ...
}

// === Main Code ===
hoistedFunc();

Easier to direct new developers to avoid confusion.

Tip 2: Comment Key Hoisting Lines

Identify key calls that seem unintuitive or easy to break:

// Works due to hoisting!
hoistedFunction();

function hoistedFunction() {
  // ...
} 

Helps avoid assumptions around execution order and preventing future bugs.

Tip 3: Compare Execution With/Without Hoisting

Mentally comparing real code execution flow against strictly line-by-line can illustrate hoisting impact:

// With hoisting
hoistedFunc(); // Runs!

// Without hoisting  
// hoistedFunc(); // Uncaught ReferenceError
function hoistedFunc() {
 // ...   
}

Reinforcing the gap helps cement the concepts.

Key Takeaways

We‘ve covered a full gamut on function hoisting in JavaScript – from what hoisting is under the hood to practical applications and interview questions.

The key takeaways are:

  • Function declarations are fully hoisted, declarations + definitions
  • Function expressions only hoist the variable declaration
  • Avoid declarations in blocks as behavior becomes unclear
  • Properly used, hoisting enables cleaner code flow and organization
  • Interviews frequently test resolution order between variables and functions during hoisting

I hope this guide has helped explain function hoisting in detail. Let me know if you have any other related questions!

Understanding hoisting serves as a gateway into further mastering both JavaScript and front end interviews.

Similar Posts