Hoisting in JavaScript: Still Confusing in 2025?

Posted by

A modern, developer-friendly guide to one of JavaScript’s oldest quirks — explained with real-world examples.

A modern, developer-friendly guide to one of JavaScript’s oldest quirks — explained with real-world examples.

Introduction

Let’s be honest: JavaScript hoisting is one of those things every dev learns once… and then forgets until it bites them again.

Maybe you’ve seen this before:

console.log(x); // undefined
var x = 10;

Why doesn’t that throw an error?

Or this:

console.log(y); // ❌ ReferenceError
let y = 20;

Wait, why is y different from x? Aren’t both variables?

Hoisting has been around since JavaScript’s earliest days, but ES6 (2015) and beyond introduced new twists with let, const, and modern function declarations. Fast forward to 2025, and devs still ask the same question: What exactly gets hoisted, and how should I think about it?

In this post, we’ll demystify hoisting step by step — no jargon, no hand-waving. By the end, you’ll have a clear mental model you can apply in real projects.


1. What Is Hoisting, Really?

At its core: Hoisting is JavaScript’s way of moving declarations to the top of their scope during the compilation phase.

  • Declarations (like var, function) are processed before any code runs.
  • This means you can sometimes use a variable or function before it’s written in the code.

But the tricky part? Not everything gets hoisted the same way.

👉 Think of it like this:
JavaScript makes a shopping list of variables/functions before running your code. But for some items, it fills in a placeholder (undefined), while for others, it refuses to give you anything until it’s ready.


2. var Hoisting: The OG Behavior

Classic var declarations are hoisted to the top of their function scope and initialized with undefined.

console.log(a); // undefined
var a = 5;
console.log(a); // 5

Why? Because internally, the engine treats your code like this:

var a;           // declaration hoisted
console.log(a); // undefined
a = 5; // assignment stays in place
console.log(a); // 5

👉 Real-world impact: Easy to accidentally overwrite values or create “ghost” variables if you forget you declared them later.


3. let & const: The Temporal Dead Zone (TDZ)

let and const are also hoisted — but without initialization. Until the declaration line runs, the variable is in the Temporal Dead Zone (TDZ).

console.log(b); // ❌ ReferenceError
let b = 10;

Behind the scenes:

  • Declaration is hoisted, but no default undefined.
  • Accessing it before initialization triggers an error.

👉 Why TDZ matters: It prevents bugs caused by using variables before they’re ready.


4. Function Declarations vs Function Expressions

Here’s where confusion skyrockets.

Function Declaration → Fully Hoisted

sayHi(); // ✅ Works
function sayHi() {
console.log("Hello!");
}

The entire function is hoisted — name and body.


Function Expression → Not Hoisted

sayBye(); // ❌ ReferenceError
var sayBye = function () {
console.log("Bye!");
};

Only the var is hoisted (initialized as undefined), not the function body.


Arrow Functions (with let/const) → TDZ Rules

sayArrow(); // ❌ ReferenceError
const sayArrow = () => console.log("Arrow!");

👉 Rule of thumb: If it’s a declaration (function foo() {}), it’s hoisted. If it’s assigned to a variable (const foo = () => {}), it follows variable hoisting rules.


5. Class Hoisting (Bonus for 2025 Devs)

Classes behave like let and const: they are hoisted but live in the TDZ.

const c = new Car(); // ❌ ReferenceError
class Car {}

👉 Gotcha: Unlike functions, classes must be defined before use.


6. Real-World Example: Debugging a Hoisting Bug

Imagine you’re writing a Node.js config loader:

console.log(config); // undefined?
var config = loadConfig();

function loadConfig() {
return { env: "prod" };
}

This logs undefined, not your config. Why? Because var config got hoisted, but the assignment didn’t.

✅ Fix with const:

const config = loadConfig();
console.log(config); // { env: "prod" }

Now there’s no room for confusion — either the config exists, or the program errors.


7. A Mental Model That Actually Works

Instead of memorizing rules, use this mental shortcut:

  • var → Hoisted, initialized with undefined.
  • let/const → Hoisted, but “locked” in TDZ until declaration.
  • function declaration → Fully hoisted (body + name).
  • function expression/arrow/class → Hoisted like let/const (TDZ).

👉 Think of it as levels of readiness:

  1. function foo() {} → Fully ready.
  2. var x → Exists, but empty.
  3. let/const or class → Exists but off-limits until initialized.

8. Why Hoisting Still Matters in 2025

You might ask: “Why care? My linter yells at me anyway.”

True, ESLint and TypeScript catch many hoisting issues. But:

  • You’ll still read old codebases full of var.
  • Frameworks (React, Next.js, Node) can behave unexpectedly if you don’t understand initialization order.
  • Debugging runtime errors often requires knowing why something is undefined vs throwing a ReferenceError.

👉 Understanding hoisting isn’t about memorization — it’s about mental clarity when debugging weird bugs.


9. Best Practices in Modern Code

  • Default to const. Prevents unintentional hoisting bugs.
  • Use let only when reassignment is intentional.
  • Avoid var. Treat it as legacy (unless writing polyfills or working in old environments).
  • Keep declarations at the top. Not required, but improves readability.
  • Rely on ESLint rules like no-use-before-define.
"rules": {
"no-use-before-define": ["error", { "functions": false }]
}

10. A Quick Reference Table


Conclusion

Hoisting isn’t magic — it’s just how JavaScript preps your variables and functions before running code. The confusion comes from different rules for different declarations.

Here’s the TL;DR mental model:

  • Functions declared with function are fully hoisted.
  • var is hoisted and set to undefined.
  • let, const, and class are hoisted but locked in the TDZ.

👉 Pro Tip: If you stick to const/let and declare before using, you’ll almost never run into hoisting bugs in modern code.


Call to Action

What’s the weirdest bug you’ve hit because of hoisting?

💬 Drop your story in the comments.
🔖 Bookmark this guide for quick debugging reference.
👩‍💻 Share with your teammate who still thinks “hoisting = moving code up.”

Leave a Reply

Your email address will not be published. Required fields are marked *