๐ŸŽญ JavaScript Hoisting: The Invisible Code Reorganization You Need to Know

K ManojK Manoj
6 min read

๐Ÿค” Take a look at this JavaScript code. What do you think it outputs?

console.log(myVar); // What happens here?
console.log(myFunc()); // And here?

var myVar = "Hello World";

function myFunc() {
  return "I'm a function!";
}

If you expected errors, you might be surprised. This code actually runs without throwing any errors! The first console.log prints undefined, and the second prints "I'm a function!". Welcome to the mysterious world of JavaScript hoisting.

๐Ÿ“š What is Hoisting?

Javascript moves the variables and function declarations to the top of their containing scope during the compilation phase. The behavior exhibited by Javascript is known an Hoisting. Itโ€™s as if, the Javascript engine moves your declarations physically upward in the code you write, although thatโ€™s not what exactly happens under the hood.

Or visualize it this way: before JavaScript executes your code line by line, it takes a first pass to scan for all declarations and allocates memory for them. We have covered this concept in our previous blog where we talked about the Execution Context and itโ€™s two distinct phases. You can find the link to the blog here.

๐Ÿงช Variable Hoisting Deep Dive

The var Keyword

When you declare a variable with var, JavaScript hoists the declaration but not the initialization. Here's what really happens:

// The code you write:
console.log(name); // undefined
var name = "JavaScript";
console.log(name); // "JavaScript"

// How JavaScript interprets it:
var name; // Declaration is hoisted
console.log(name); // undefined (declared but not assigned)
name = "JavaScript"; // Assignment stays in place
console.log(name); // "JavaScript"

This is why var variables return undefined instead of throwing a ReferenceError when accessed before declaration. The variable exists in memory but hasn't been assigned a value yet.

var declarations are function-scoped, meaning they're hoisted to the top of the nearest function or global scope:

function example() {
  console.log(x); // undefined (not ReferenceError)

  if (true) {
    var x = 1;
  }

  console.log(x); // 1
}

// JavaScript interprets this as:
function example() {
  var x; // Hoisted to function top
  console.log(x); // undefined

  if (true) {
    x = 1; // Assignment stays here
  }

  console.log(x); // 1
}

let and const Behavior

Here's where things get interesting. let and const are technically hoisted too, but they behave very differently:

console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
let myLet = "Hello";

console.log(myConst); // ReferenceError: Cannot access 'myConst' before initialization
const myConst = "World";

The key difference is the Temporal Dead Zone (TDZ). While let and const declarations are hoisted, they remain uninitialized until the JavaScript engine encounters their declaration in the code. Trying to access them before that point throws a ReferenceError.

function demonstrateTDZ() {
  // TDZ starts here for 'temp'
  console.log(temp); // ReferenceError

  let temp = "Here we go!"; // TDZ ends here
  console.log(temp); // "Here we go!"
}

Unlike var, let and const are block-scoped:

function blockScopeExample() {
  console.log(x); // ReferenceError

  if (true) {
    let x = 1; // Only exists within this block
  }

  console.log(x); // ReferenceError
}

๐Ÿ› ๏ธ Function Hoisting

Function Declarations

Function declarations are completely hoisted, meaning both the function name and its implementation are moved to the top of the scope:

// This works perfectly:
greetings(); // "Hello, World!"

function greetings() {
  console.log("Hello, World!");
}

// JavaScript treats it as:
function greetings() {
  console.log("Hello, World!");
}

greetings(); // "Hello, World!"

This complete hoisting allows you to organize your code with the main logic at the top and helper functions at the bottom, improving readability.

Function Expressions and Arrow Functions

Function expressions and arrow functions follow variable hoisting rules and not function hoisting rules:

// This won't work:
sayHi(); // TypeError: sayHi is not a function

var sayHi = function() {
  console.log("Hi!");
};

// JavaScript interprets this as:
var sayHi; // undefined
sayHi(); // TypeError: sayHi is not a function
sayHi = function() {
  console.log("Hi!");
};

The same applies to arrow functions:

greet(); // ReferenceError: Cannot access 'greet' before initialization

const greet = () => {
  console.log("Greetings!");
};

๐Ÿ•ณ๏ธ Common Hoisting Gotchas

Here are some classic examples that trip up developers:

Pitfall 1: Variable Shadowing

var x = 1;

function test() {
  console.log(x); // What prints here?
  var x = 2;
  console.log(x); // And here?
}

test();

Answer: The first console.log prints undefined, not 1. The local var x declaration is hoisted, shadowing the global x.

Pitfall 2: Function vs Variable Hoisting

console.log(foo); // What happens?

var foo = "variable";

function foo() {
  return "function";
}

console.log(foo); // What about here?

Answer: The first console.log prints the function (functions are hoisted completely), while the second prints "variable" (the assignment overwrites the function). To understand this better, look at the code below to visualize how javascript treats the above piece of code as.

// Since this function is hoisted completely
function foo() {
  return "function";
}
console.log(foo); // prints function

var foo = "variable"; // here it's overwritten

console.log(foo); // prints variable

Pitfall 3: Loop Confusion

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}

This prints 3 three times because var is function-scoped, and by the time the timeouts execute, the loop has finished and i is 3.

๐Ÿ•ต๏ธ Real-World Debugging

Hoisting can cause subtle bugs that are hard to track down:

function processUser(user) {
  if (user.isActive) {
    var userData = getUserData(user.id);
    // ... process userData
  }

  console.log(userData); // undefined, not ReferenceError
  // This might cause issues downstream
}

The userData variable is hoisted to the function top, so it exists even when the if condition is false, potentially masking bugs.

๐Ÿง Understanding the Why

Hoisting exists for historical reasons. JavaScript was designed to be forgiving and allow developers to write code in a more natural, top-down manner. Function hoisting, in particular, enables mutual recursion and better code organization:

function isEven(n) {
  return n === 0 || isOdd(n - 1);
}

function isOdd(n) {
  return n !== 0 && isEven(n - 1);
}

Without hoisting, this mutual recursion wouldn't be possible with function declarations.

๐ŸŽฏ Conclusion

Hoisting is one of JavaScript's most misunderstood features, but understanding it is crucial for writing reliable code and debugging issues effectively. Here are the key takeaways:

  • var declarations are hoisted and initialized as undefined.

  • let and const are hoisted but remain in the Temporal Dead Zone until declaration.

  • Function declarations are completely hoisted, function expressions follow variable rules.

  • Modern best practices favor const/let over var and explicit declaration order.

  • Understanding hoisting helps you avoid common pitfalls and write more predictable code.

The next time you encounter unexpected undefined values or mysterious ReferenceErrors, remember to think about hoisting. It might just be the invisible code reorganization causing the confusion!

โœ๏ธ Missed My Last Blog?

Read it here.

๐Ÿ” Get clarity on how JavaScript prepares to run your code, what really happens during the creation and execution phases, and how the call stack handles function calls behind the scenes.

๐Ÿค Connect with Me

If you enjoyed this blog and want to stay updated with more JavaScript insights, developer tips, and tech deep-dives โ€” feel free to connect with me across platforms:

๐Ÿ”— LinkedIn: Connect with me on LinkedIn

๐Ÿ“ Medium: Follow me on Medium

๐ŸŒ Hashnode: Check out my Hashnode blog

I appreciate your support and look forward to connecting with fellow devs, learners, and curious minds like you! ๐Ÿš€

1
Subscribe to my newsletter

Read articles from K Manoj directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

K Manoj
K Manoj

Backend Web Developer | Security Enthusiast |