Function Hoisting vs Variable Hoisting in JavaScript: Key Differences Explained
Function hoisting lifts the entire function declaration (name and body) to the top of its scope, making it callable immediately, while variable hoisting (var) only lifts the identifier and initializes it to undefined until the assignment executes.
JavaScript’s compilation phase registers identifiers before runtime execution begins. According to the You Don't Know JS book series by getify, this behavior—commonly called hoisting—operates differently for function declarations than for variables declared with var. Understanding these mechanics is critical for avoiding undefined values and ReferenceErrors in your code.
What is Hoisting?
Hoisting refers to the compile-time action where the JavaScript engine registers variable and function declarations to their respective scopes before executing any statements. As detailed in scope-closures/ch5.md, this process moves the "registration" of identifiers to the top of their enclosing function or global scope, though it behaves distinctly depending on whether the identifier is a function declaration or a variable.
Function Hoisting: Declaration and Initialization
Function hoisting is the more aggressive of the two mechanisms. When the engine encounters a function declaration (not an expression), it hoists both the identifier and the function body to the top of the scope.
According to the source code in scope-closures/ch5.md (lines 33-35), "A function declaration’s name identifier is registered at the top of its scope, and it’s additionally auto‑initialized to that function’s reference."
This means you can invoke the function before its literal position in the code:
// Call before the declaration – works because of function hoisting
greet(); // → "Hello!"
function greet() { // Function declaration
console.log('Hello!');
}
Only function declarations (function foo(){}) appearing outside of block statements receive this treatment. Function expressions assigned to variables are subject to variable hoisting rules instead.
Variable Hoisting with var
Variable hoisting behaves more conservatively. When the engine processes a var declaration, it registers the name at the top of the scope but initializes it to undefined. The actual assignment remains at its original runtime position.
As stated in scope-closures/ch5.md (lines 64-65): "A var variable is also hoisted, and then auto‑initialized to undefined."
This creates a "temporal gap" where the identifier exists but holds no useful value:
console.log(num); // → undefined (variable hoisted, value not yet assigned)
var num = 42; // Assignment happens at runtime
Key Differences Between Function and Variable Hoisting
The distinction between these mechanisms affects when and how you can safely reference identifiers:
| Aspect | Function Hoisting | Variable Hoisting (var) |
|---|---|---|
| What is hoisted? | The entire function declaration (name and implementation). | Only the variable’s name; initialized to undefined. |
| When can you use it? | Immediately anywhere in the same scope, even before the declaration. | Anywhere, but reads return undefined until the assignment executes. |
| Syntax requirements | Only function declarations (function foo(){}) outside blocks. |
Any var declaration regardless of position. |
| Resulting behavior | The identifier is bound to the function object immediately. | The identifier exists but yields undefined until assigned. |
Function hoisting enables a top-down coding style where you can place helper functions below the main logic and call them earlier. Variable hoisting can introduce subtle bugs because variables appear defined while actually holding undefined.
Code Examples and Edge Cases
Calling Functions Before Declaration
Function declarations are fully available throughout their scope:
calculate(5, 3); // → 8
function calculate(a, b) {
return a + b;
}
The undefined Variable Behavior
Variables declared with var exist but contain no value until their line executes:
console.log(message); // → undefined
var message = "Hello";
console.log(message); // → "Hello"
Function Expressions Are Not Hoisted
Assigning a function to a const or let creates a function expression that follows variable hoisting rules (or Temporal Dead Zone rules for let/const), not function hoisting:
try {
shout(); // ReferenceError – not hoisted
} catch (e) {
console.error(e);
}
const shout = function() {
console.log('Hey!');
};
Only function declarations are hoisted; expressions behave like ordinary variable assignments where the identifier is not initialized until the line executes.
When Function and Variable Names Collide
When both a function declaration and a var declaration share the same name, function hoisting takes precedence initially, but subsequent assignment overwrites it:
console.log(foo); // → [Function: foo] (function hoisted first)
function foo() { console.log('function'); }
var foo = 10; // var hoisted but doesn't re-initialize; assignment overwrites
foo(); // TypeError: foo is not a function (now holds the number 10)
The function declaration hoists first, giving foo a function value. The subsequent var foo declaration also hoists the name but does not re‑initialize it; the later assignment to 10 replaces the function reference.
Summary
- Function hoisting moves the entire declaration (name + body) to the top of the scope and immediately binds the identifier to the function object, allowing invocation before the declaration line.
- Variable hoisting (
var) registers only the identifier at the top of the scope and auto-initializes it toundefined, leaving the assignment for runtime execution. - Function expressions (including arrow functions assigned to variables) are not hoisted as functions; they follow variable hoisting or Temporal Dead Zone rules depending on the declaration keyword.
- Both mechanisms are compile-time actions that affect runtime execution, but only function hoisting supplies the usable value instantly.
Frequently Asked Questions
Do let and const hoist like var?
No. While let and const declarations are technically registered during the compile phase, they are not initialized until their declaration statement is evaluated. They exist in the Temporal Dead Zone (TDZ) from the start of the block until the declaration line, causing a ReferenceError if accessed too early. This differs from var, which initializes to undefined immediately upon hoisting.
Why can I call a function before declaring it but not access a variable?
Because function hoisting initializes the identifier immediately with the actual function reference, whereas variable hoisting initializes only with undefined. According to scope-closures/ch5.md, function declarations are "auto‑initialized to that function’s reference" during the compile phase, while var declarations are "auto‑initialized to undefined" and must wait for runtime assignment.
Are function expressions hoisted if I use var instead of const?
No. Whether you use var, let, or const, a function expression is treated as a variable assignment, not a function declaration. The variable identifier itself will hoist (with undefined for var or TDZ for let/const), but the function body is not available until the assignment statement executes.
Is function hoisting considered good practice?
It depends on coding style preferences. The You Don't Know JS series notes in scope-closures/apA.md that function hoisting enables a "top-down" readability style where main logic appears first and implementation details follow. However, many teams prefer declaring functions before use to avoid confusion, or they use function expressions to prevent accidental reliance on hoisting mechanics.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →