Why JavaScript Feels Out of Order: Understanding Hoisting
Hoisting in JavaScript is the result of the engineâs creation phase, where variables and functions are registered in memory before execution, leading to behaviors like var being initialized as undefined, let/const existing in a temporal dead zone, and function declarations being fully available before they appear in code.
Thereâs a moment when learning JavaScript where something just doesnât feel right.
You write code from top to bottomâŚ
but JavaScript behaves as if it read something earlier than it should.
A variable exists before itâs declared.
A function works before itâs defined.
And you start wondering:
Is JavaScript ignoring the order of my code?
Not exactly.
Itâs following a different process.
JavaScript Doesnât Execute Immediately
When JavaScript runs your code, it doesnât just execute line by line.
Before anything runs, it goes through a setup step.
A preparation phase.
In this phase, it scans your code and registers:
- variables
- functions
- scope
Only after that does execution begin.
This is what creates the behavior we call hoisting.
Hoisting Is Not Moving Code
A common explanation says:
âJavaScript moves declarations to the top.â
Thatâs not really what happens.
Nothing is physically moved.
Instead, JavaScript builds a mental map of your code before running it.
It allocates memory.
It prepares references.
And that preparation changes how your code behaves.
Why var Becomes undefined
Consider this:
console.log(a)
var a = 10At first glance, it feels like this should fail.
But during the setup phase, JavaScript registers a and gives it a default value:
var a = undefinedSo when execution starts:
console.log(a) // undefined
a = 10This behavior comes from early JavaScript design.
The language prioritized flexibility.
Instead of throwing errors, it allowed variables to exist earlyâŚ
even if they werenât fully initialized.
Why let and const Throw Errors
Now compare that with:
console.log(a)
let a = 10This time, you get a ReferenceError.
Why?
Because modern JavaScript introduced a stricter rule.
Variables declared with let and const are still registered during setupâŚ
but they are not initialized.
They exist in a state called the Temporal Dead Zone.
From the start of the scope until the declaration line, the variable is:
- known to JavaScript
- but not accessible
This was a deliberate design decision.
It prevents accidental usage before a variable is ready.
In other words, it trades flexibility for safety.
Why Function Declarations Just Work
Now consider this:
sayHello()
function sayHello() {
console.log("Hello")
}This works perfectly.
Because during the setup phase, function declarations are fully stored in memory.
Not just their nameâŚ
but their entire implementation.
So when execution begins, the function is already available.
This behavior exists because functions often need to be called before their definition, especially in older JavaScript patterns.
Why Function Expressions Fail
Now look at this:
sayHello()
var sayHello = function () {
console.log("Hello")
}This fails with a TypeError.
Even though it looks similar.
The reason is subtle but important.
This is not a function declaration.
Itâs a variable assignment.
During setup, JavaScript treats it like this:
var sayHello = undefinedSo when execution starts:
sayHello() // undefined is not a functionOnly later does the function get assigned.
The difference is not about syntax.
Itâs about how JavaScript categorizes the declaration.
The Pattern Behind Everything
If you step back, all of these behaviors follow one rule.
JavaScript prepares memory differently depending on what you declare.
varâ created and initialized as undefinedlet/constâ created but not initialized (TDZ)- function declaration â fully initialized
- function expression â treated as a variable
Each keyword is a signal.
It tells JavaScript how to prepare that piece of code before execution.
The Real System Behind Hoisting
Underneath all of this is something deeper.
Execution context.
When JavaScript runs your code, it creates an environment where:
- variables are stored
- functions are stored
- scope is defined
This environment is built before execution.
And hoisting is simply the visible effect of that process.
Why This Design Exists
Early JavaScript was designed to be forgiving.
It allowed flexibility, even if it meant some confusion.
Later versions introduced stricter rules like let and const to reduce bugs.
So what youâre seeing is not inconsistency.
Itâs evolution.
A language balancing:
- flexibility
- safety
- backward compatibility
A Better Way to Think About It
Instead of thinking:
âWhy is JavaScript ignoring order?â
Think:
âHow did JavaScript prepare this before running it?â
That shift makes everything clearer.
Because the behavior is not random.
Itâs the result of a system working exactly as designed.
A Final Thought
Hoisting isnât a trick.
Itâs a glimpse into how JavaScript actually works.
It reveals that your code isnât just executedâŚ
itâs first understood.
And once you see thatâŚ
the confusion fades.
Because now youâre not just reading codeâŚ
youâre thinking like the engine.