Hoisting in JavaScript

Hoisting in JavaScript describes how declarations are processed before the code starts running line by line. The word makes many beginners imagine that JavaScript physically moves code to the top of the file. That is not what actually happens. JavaScript creates an execution context first, registers declarations, and then executes the program. The visible effect is that some names seem to exist before the line where you wrote them.

If you understand hoisting as a setup phase rather than a text moving trick, the topic becomes much easier. Function declarations are fully available early. Variables declared with `var` are known early but start with `undefined`. Variables declared with `let` and `const` are also registered early, but they cannot be used before their declaration line because of the temporal dead zone.

Why hoisting matters

Hoisting matters because it explains many confusing results in JavaScript. If a developer logs a `var` variable before assignment and sees `undefined`, that is a hoisting behavior. If a function declaration works before its position in the file, that is also hoisting. If a `let` variable throws a `ReferenceError` even though the declaration appears later in the same scope, hoisting is still part of the story.

Without a clear model of hoisting, code review becomes guesswork. Developers may think JavaScript is acting randomly when in reality the engine is following consistent rules. Understanding those rules helps prevent subtle bugs and makes it easier to reason about older code that still uses `var`.

Declaration typeBefore declaration lineBehavior
Function declarationAccessibleCan be called before its line appears
`var`AccessibleExists with value `undefined` until assignment
`let`Not usableIn temporal dead zone until declaration line
`const`Not usableIn temporal dead zone until declaration line
Function expression assigned to variableDepends on variable kindThe variable rules apply, not function declaration rules

Hoisting with var

Variables declared with `var` are hoisted to the top of their function scope. During the creation phase, JavaScript registers the variable name and initializes it with `undefined`. The assignment still happens later, when execution reaches that line. This is why using a `var` variable too early does not throw an error but also does not give the final value.

console.log(total);
var total = 50;
console.log(total);

JavaScript treats that code roughly as if the declaration were known first and the assignment happened later. The first log prints `undefined`, not `50`, because only the declaration is prepared early. The assignment is still waiting for normal execution flow.

var message;
console.log(message);
message = "ready";
console.log(message);

This behavior is one reason modern JavaScript avoids `var` in new code. Seeing `undefined` from a variable that appears later can hide mistakes. Code becomes harder to read because a variable name seems valid before the reader reaches the line where it should logically start to exist.

Hoisting with function declarations

Function declarations are hoisted more completely than `var` variables. JavaScript registers the function name and the function body during the creation phase, so the function can usually be called before the declaration line in the source file. This is why many older JavaScript codebases place helper functions near the bottom without breaking execution.

greet();

function greet() {
  console.log("Welcome");
}

This works because `greet` is a function declaration. If the same function were written as a function expression assigned to a variable, the result would change. Developers often confuse these two forms even though hoisting treats them differently.

Function expressions are different

When a function is stored in a variable, the hoisting behavior follows the variable declaration, not the function body. If the variable uses `var`, the name exists as `undefined` before assignment. If the variable uses `let` or `const`, accessing it before the declaration line throws a `ReferenceError`.

console.log(run);
var run = function () {
  console.log("running");
};

The first log prints `undefined` because `run` behaves like a hoisted `var` variable. Trying to call `run()` before assignment would fail because JavaScript would be trying to call `undefined` as a function. This is very different from a normal function declaration.

let, const, and the temporal dead zone

Variables declared with `let` and `const` are also registered during the creation phase, but they are not initialized for use right away. The period between entering the scope and reaching the declaration line is called the temporal dead zone. During that time, the variable exists in the scope rules, but it is not accessible.

// console.log(score);
let score = 95;
console.log(score);

// console.log(rate);
const rate = 0.18;
console.log(rate);

If you remove the comment markers from the first two logs, JavaScript throws a `ReferenceError`. This is safer than silently giving `undefined` because it exposes the mistake immediately. Modern JavaScript favors this behavior because it makes variable lifetime clearer and discourages accidental early access.

Hoisting inside functions and blocks

Hoisting depends on scope. A `var` declaration inside a function is hoisted to the top of that function, not to the top of the whole file. A `let` or `const` inside a block belongs to that block. This means hoisting does not ignore scope boundaries. It works within them.

function checkOrder() {
  console.log(status);
  var status = "loaded";
}

checkOrder();

Here the `status` variable is hoisted only within `checkOrder`. Code outside the function cannot see it. That detail matters because many hoisting problems are really scope problems mixed with declaration behavior.

Common mistakes about hoisting

  • Thinking JavaScript literally moves lines of code upward.
  • Assuming function expressions behave like function declarations.
  • Believing `let` and `const` are not hoisted at all.
  • Ignoring the role of scope when describing hoisting behavior.

The most accurate explanation is that JavaScript creates the execution context first, then runs the code. During creation, declarations are processed. During execution, assignments and statements run in order. This two phase model explains nearly every hoisting example clearly.

Best practices around hoisting

In practical development, the best defense is simple structure. Declare variables close to where they are used. Prefer `const` and `let` over `var`. Use function declarations when early availability is helpful, and use function expressions only when the assignment behavior is intentional. These habits make hoisting less surprising because the code reads closer to the way it executes.

Hoisting should be something you understand, not something you rely on as a clever trick. Code is easier to maintain when names appear before they are used, even if JavaScript would technically allow some of them to work earlier.

FAQ

Is var hoisted with its assigned value?

No. The declaration is prepared early, but the assignment still happens later during normal execution. That is why the early value is `undefined`.

Are let and const hoisted?

Yes. They are registered during the creation phase, but they remain unavailable until the declaration line is reached, which is why the temporal dead zone exists.

Why do function declarations work before their position in code?

Function declarations are hoisted with their callable function body, so JavaScript can resolve the function name before execution reaches that line.

Execution context and hoisting together

The cleanest way to understand hoisting is through the idea of an execution context. Before JavaScript starts executing statements, it prepares the current context. During that preparation step, function declarations are stored, and variable declarations are registered. Then the execution step begins, where assignments and expressions run in the order they appear. This two phase model is more accurate than saying JavaScript simply moves declarations upward.

When developers learn this model, many examples become predictable. A function declaration works early because its body was registered in the creation phase. A `var` variable shows `undefined` because its name was registered but the assignment did not run yet. A `let` variable throws an error because JavaScript knows the name, but the declaration line has not activated it for use. The engine is not being inconsistent. It is following a precise setup rule.

Hoisting and code quality

In professional codebases, the goal is not to exploit hoisting tricks. The goal is to write code that remains obvious to the next reader. That is why good teams still place declarations before use, even when JavaScript would technically allow a function declaration to be called earlier. Readable ordering reduces surprises, makes refactoring safer, and helps new developers understand the file without holding too many special rules in their head at once.

Hoisting knowledge is still essential, especially when debugging old code or reviewing mixed styles in large projects. But the practical lesson is clear: understand hoisting deeply, then write code that does not force other people to depend on it to follow the logic.

Reading hoisting examples correctly

When you read hoisting examples, separate three questions clearly. First, was the name registered during the creation phase. Second, was it initialized for safe access. Third, has normal execution reached the assignment line yet. Most confusion disappears once those three questions are answered in order. A declaration can exist without holding the final value, and a name can be registered without being safely accessible before the declaration line.

This is why the topic is easier to master through the engine model than through memorized exceptions. Hoisting is not a random collection of strange examples. It is a predictable result of how JavaScript prepares scope before running statements.

For day to day coding, the most useful takeaway is to keep declarations visible before use and to prefer modern declaration styles. Then hoisting remains a concept you understand for debugging and review rather than a source of surprises in new code. That approach keeps the mental model accurate without forcing the reader to depend on engine quirks just to follow normal program flow.