What Is JavaScript Hoisting

What is JavaScript Hoisting

Ready to start learning? Individual Plans →Team Plans →

What Is JavaScript Hoisting? A Practical Guide to Declarations, Scope, and the Temporal Dead Zone

If you have ever seen hoisted meaning in JavaScript and wondered why a variable prints undefined before its declaration, you are looking at one of the first real “gotchas” in the language. The behavior is not random. JavaScript prepares declarations before it runs your code, and that affects hoisting in JavaScript in ways that can help you or trip you up.

This guide breaks down javascript hoisting in plain language. You will see how hoisted JavaScript declarations work, why var behaves differently from let and const, how the temporal dead zone protects you from early access, and what practical habits reduce bugs in real projects.

Introduction to JavaScript Hoisting

JavaScript hoisting is the language’s behavior of making declarations available before the interpreter reaches their line in the source code. That does not mean JavaScript literally moves lines of code around. It means the engine creates scope and registers declarations before it executes the rest of the file.

This is often the first surprising concept developers meet because the code appears to run “out of order.” A variable can exist before it is assigned, and a function can be called before its definition appears. Once you understand the difference between declarations and assignments, the weirdness starts to make sense.

There are three behaviors to keep separate:

  • Variable hoisting with var, where the declaration is available early but the value is not.
  • Function hoisting, where function declarations are available with their body.
  • Block-scoped declarations with let and const, which are hoisted but not usable before the declaration line.

The practical goal is simple: understand what is available, when it becomes available, and how to avoid use-before-declaration bugs. If you work in JavaScript long enough, hoisting will show up in debugging sessions, code reviews, and production fixes.

Hoisting does not rewrite your source code. It is the result of how JavaScript creates scope and registers declarations before execution starts.

MDN Hoisting Glossary is a good reference for the formal definition, but the real value is knowing how this behavior affects everyday code.

How JavaScript Execution Works Behind the Scenes

JavaScript execution is usually described in two phases: the creation phase and the execution phase. During creation, the engine sets up the execution context, builds scope, and registers declarations. During execution, it runs the statements line by line.

That early setup is why some identifiers seem to exist before their line of code appears. The engine scans for declarations, not assignments. A statement like var x = 10; is treated as two parts: the declaration of x, and later the assignment of 10.

Declaration first, assignment later

This difference matters because declarations become part of the scope before execution starts. Assignments do not. So when the engine reaches console.log(x); before x = 10, the name x already exists, but its value is still undefined.

That behavior is tied to scope creation. It is not a literal text transformation, and it is not the same as reordering code. Understanding that distinction helps you reason about nested functions, blocks, loops, and closures without relying on guesswork.

For a deeper official explanation of execution behavior and scope, see MDN JavaScript Declarations and the ECMAScript language reference at ECMAScript Language Specification.

Note

Hoisted in JavaScript is easiest to understand if you separate “name exists” from “value is ready.” That one distinction explains most hoisting bugs.

Variable Hoisting with Var

var is the classic example of hoisting because the declaration is moved to the top of its function or global scope during creation. Only the declaration is hoisted. The assignment stays where you wrote it.

That is why this code logs undefined instead of throwing an error:

console.log(x);<br>var x = 10;

By the time console.log runs, JavaScript knows the name x. It has not yet assigned 10, so the value is undefined. That difference is critical.

Undefined versus ReferenceError

undefined means the identifier exists and currently has no assigned value. ReferenceError means JavaScript cannot access the identifier in that scope at all. Those are not the same, and confusing them leads to bad debugging conclusions.

For example, a var declaration can produce undefined before assignment. A missing variable or a block-scoped variable outside its scope can produce ReferenceError. That difference is one reason hoisting with var can be confusing in large functions.

var hoisting can also leak variables into an entire function, which makes accidental reuse easier. In modern code, that is usually a problem, not a benefit.

Behavior Result
var declaration before assignment undefined is returned when read early
Missing or inaccessible identifier ReferenceError is thrown

For official language behavior, MDN’s var statement page is a solid reference.

Function Hoisting and Why It Often Feels More Predictable

Function declarations are fully hoisted, including their body. That means you can call a function before the source code reaches the function definition, and it works because the function object is created during scope setup.

Example:

sayHello();<br><br>function sayHello() {<br>  console.log("Hello");<br>}

This behavior feels more predictable than var hoisting because the function is ready to use, not just declared. In small scripts or utility-heavy files, that can make code organization more flexible.

Function declarations versus function expressions

A function declaration is hoisted fully. A function expression is not. If you write const sayHello = function() { ... }, the variable name exists according to the rules of const, but the function value is not available until execution reaches the assignment.

That means these two forms do not behave the same way, even though both create functions:

  • Function declaration: fully hoisted and callable before its line appears.
  • Function expression: depends on the variable declaration rules of var, let, or const.

This is where hoisting can become confusing if there are multiple functions with similar names in the same scope. The safest approach is to use function declarations intentionally, not casually.

For official reference, see MDN Function Declarations.

Function declarations are the most “hoist-friendly” part of JavaScript. That convenience is useful, but only when the scope is simple and the naming is clear.

Let, Const, and the Temporal Dead Zone

let and const are technically hoisted, but they are not initialized immediately. That is why reading them before the declaration line throws a ReferenceError instead of quietly giving you undefined.

The period between entering scope and reaching the declaration line is called the temporal dead zone. During that time, the variable exists in the scope but cannot be used. This design prevents accidental reads before the variable is ready.

Why the temporal dead zone matters

The temporal dead zone is a safety feature. It catches logic errors early, especially in functions and blocks where a variable name might be reused or shadowed. Instead of silently continuing with bad data, JavaScript stops the code and makes the error obvious.

Compare these behaviors:

  • var allows an early read and returns undefined.
  • let throws if accessed before the declaration line.
  • const behaves like let for timing, but also requires initialization at declaration.

That is why const hoisting is often misunderstood. The identifier is known to the scope, but it is unusable until the declaration executes. This is one of the most important differences between old-school JavaScript and modern JavaScript.

Key Takeaway

let and const are safer because they fail loudly. They do not let you read a variable before it is ready, which reduces subtle bugs.

See MDN let and MDN const for the official language behavior.

Function Scope vs Block Scope in Hoisting

Function scope means a variable is visible throughout the function where it was declared. var follows this model. Once declared inside a function, it can be referenced anywhere in that function, even before the line that appears to define it.

Block scope means a variable is visible only inside the nearest pair of curly braces. let and const follow this model. That includes if statements, loops, try blocks, and other code blocks.

How scope changes hoisting behavior

If you declare var inside an if block, the declaration still belongs to the surrounding function, not the block. That can make the variable visible outside the block unexpectedly. With let and const, the variable stays inside the block and cannot leak.

Example of why this matters in loops:

  • var in a loop can create one shared loop variable across iterations.
  • let in a loop creates a fresh binding per iteration, which is usually what you want.

This is one reason modern JavaScript code prefers block scope. It matches the visual structure of the code much more closely, so the behavior is easier to predict during debugging and maintenance.

For broader context on scope and execution, the MDN Closures guide helps explain how scope is preserved across function boundaries.

Common Hoisting Mistakes and Misconceptions

The biggest myth is that “everything is moved to the top.” That is not true. Only declarations are processed early, and even then the rules differ by declaration type. Assignments, initial values, and executable statements still run where you wrote them.

Another common mistake is treating undefined like an error. It is often not an error at all. It can simply mean you read a var before assignment. That distinction matters when reading logs, tracing bugs, or handling unexpected values.

Why var causes more trouble in modern code

var can create hidden scope problems, especially in larger functions and nested blocks. Because it does not respect block scope, it can leak names into places you did not intend. That makes debugging harder when the same identifier is reused.

Function declarations and function expressions also get mixed up often. A function declaration is hoisted fully. A function expression depends on the variable used to store it. If that variable is let or const, the temporal dead zone applies.

One more misconception: hoisting does not change the logical order of execution. It changes how declarations are prepared before execution begins. Your statements still run in sequence.

Do not use hoisting as a design strategy. Understand it, then write code that stays clear even when someone else reads it cold.

For formal guidance on safe syntax patterns, the ESLint no-use-before-define rule is useful in real projects.

Practical Examples That Show Hoisting in Real Code

Reading about hoisting is useful, but seeing it in code makes the rules stick. The goal is to walk through the engine’s behavior step by step so the result is predictable, not magical.

Example with var before assignment

function demo() {<br>  console.log(message);<br>  var message = "ready";<br>  console.log(message);<br>}

What happens here?

  1. JavaScript creates the function scope.
  2. message is registered because of var.
  3. Its value starts as undefined.
  4. The first log prints undefined.
  5. The assignment runs and stores "ready".
  6. The second log prints ready.

Now compare a function declaration and a function expression:

run();<br>function run() { console.log("A"); }<br><br>start();<br>const start = function() { console.log("B"); };

The first call works. The second fails until the assignment is reached, because the variable exists under const rules but the function value is still inside the temporal dead zone.

For loop behavior, var can create a classic surprise:

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

Because var is function-scoped, the callbacks often see the final value. Replacing var with let changes the binding per iteration, which is usually the intended behavior.

That is why line-by-line reading can be misleading. You need to consider scope creation and initialization timing at the same time.

MDN for statement is a useful official reference for loop scope behavior.

How to Write Safer JavaScript by Avoiding Hoisting Pitfalls

The safest modern habit is simple: prefer let and const over var. That gives you block scope, clearer intent, and fewer accidental leaks across a function.

When readability matters, declare variables near the top of their scope. That is not because hoisting requires it. It is because humans read code linearly, and early declarations make dependencies easier to spot.

Practical coding habits

  • Use const by default when the binding should not change.
  • Use let when reassignment is expected.
  • Avoid var unless you are maintaining legacy code that depends on it.
  • Use function declarations intentionally when hoisting is helpful and simple.
  • Do not depend on hoisting to make code “work later.”

Linting and code review are also important. ESLint can catch use-before-define issues early, and editor warnings help teams keep code consistent. In a collaborative codebase, explicit ordering is usually safer than clever structure.

Pro Tip

If you are refactoring old JavaScript, convert var to let or const one scope at a time. That makes hoisting-related bugs easier to isolate.

For coding standard guidance, see ESLint and the official MDN JavaScript guide.

Tools and Debugging Strategies for Hoisting Issues

When hoisting causes a bug, the fastest fix usually comes from tracing scope and initialization timing, not from guessing. Browser developer tools and the Node.js console are enough for most cases.

Start by checking whether the problem is a ReferenceError or a value of undefined. That tells you whether the identifier is inaccessible or simply not assigned yet. Stack traces help here, but only if you read the line that actually failed.

Debugging workflow

  1. Reproduce the issue in the smallest possible snippet.
  2. Add temporary console.log statements before and after the suspected declaration.
  3. Check whether the scope is function-scoped or block-scoped.
  4. Look for shadowing, where a local variable hides an outer one.
  5. Run ESLint with rules such as no-use-before-define.

Formatters and editor warnings also help because they make structure more visible. If a file has deeply nested blocks, the way it is indented often reveals scope mistakes before runtime does.

For official documentation on debugging and console usage, refer to MDN Console and Node.js Console.

Most hoisting bugs are not really hoisting bugs. They are scope bugs, timing bugs, or assumptions about initialization.

When Hoisting Is Helpful and When It Is Harmful

Hoisting is useful when you want to organize utility functions at the bottom of a file while calling them earlier, especially in small scripts or simple modules. It can also improve conceptual flow: main logic first, helper details later.

That said, hoisting becomes harmful when the codebase relies on it too much. If a reader has to mentally simulate JavaScript’s creation phase just to understand the file, the code is doing too much work for the reader.

When hoisting helps

  • Utility functions are grouped together for readability.
  • The top of the file shows the business flow before implementation details.
  • Function declarations are used consistently and clearly.

When hoisting hurts

  • Variables are read before assignment because the author assumed they were already initialized.
  • var leaks values across block boundaries.
  • Function names are duplicated or shadowed in the same scope.
  • Debugging becomes dependent on knowing engine behavior instead of reading code directly.

Explicit ordering is usually safer in collaborative codebases because it reduces surprise. The best practice is not to avoid hoisting entirely. It is to understand it well enough that you do not depend on it for basic correctness.

For standards around maintainable code and predictable behavior, the JavaScript style recommendations published through the MDN JavaScript documentation remain a reliable reference.

Conclusion

The core idea behind hoisted meaning in JavaScript is straightforward: declarations are prepared before execution, but assignments still happen where you wrote them. That is why hoisting in JavaScript can produce undefined, enable early function calls, or trigger a ReferenceError depending on the declaration type.

Here is the practical version to remember:

  • var is function-scoped and hoisted with undefined initialization.
  • Function declarations are fully hoisted.
  • let and const are hoisted but blocked by the temporal dead zone until their declaration runs.
  • Block scope makes modern code easier to predict and debug.

If you want cleaner JavaScript, write explicit code, prefer block-scoped declarations, and use hoisting as background knowledge rather than a design technique. That approach prevents a lot of subtle bugs and makes your code easier for the next developer to read.

For more practical JavaScript guidance and IT training content from ITU Online IT Training, keep building from the basics until these behaviors become second nature.

JavaScript and related terms are trademarks or registered trademarks of their respective owners.

[ FAQ ]

Frequently Asked Questions.

What is JavaScript hoisting and how does it affect variable declarations?

JavaScript hoisting is a behavior where variable and function declarations are moved to the top of their containing scope during the compilation phase before the code executes. This means that variables declared with var, let, or const are processed differently, impacting how and when they can be accessed.

For variables declared with var, the declaration is hoisted, but not the initialization. As a result, referencing a var variable before its declaration will return undefined, not an error. Conversely, variables declared with let and const are hoisted but remain in a temporal dead zone until their declaration is processed, leading to a ReferenceError if accessed prematurely.

Why do variables sometimes print as undefined before their declaration?

This occurs because of hoisting combined with the way var declarations are handled. JavaScript hoists the declaration of var variables to the top of their scope but leaves the initialization in place. Therefore, if you try to access the variable before its assignment, it returns undefined.

This behavior can be confusing for developers new to JavaScript. Understanding that only the declaration is hoisted, not the assignment, helps prevent bugs and unexpected behavior. Using let and const can avoid this issue altogether, as they are not initialized during hoisting and are in the temporal dead zone until declaration.

What is the ‘temporal dead zone’ in JavaScript?

The temporal dead zone (TDZ) is a behavior where variables declared with let and const are not accessible until their declaration has been evaluated during execution. This means that trying to access such variables before their declaration results in a ReferenceError.

The TDZ starts at the beginning of the scope (function or block) and ends at the point where the variable is declared. This mechanism enforces a stricter and more predictable scope for let and const, helping prevent bugs related to hoisting and variable access order.

How can understanding hoisting improve my JavaScript coding practices?

Understanding hoisting helps you write more predictable and bug-free JavaScript code. By knowing which declarations are hoisted and how, you can avoid common pitfalls like accessing variables before they are initialized or encountering unexpected undefined values.

It encourages best practices such as declaring variables at the top of their scope, using let and const instead of var, and being mindful of the execution order. This knowledge also assists in debugging and maintaining complex codebases, ensuring you understand how variables and functions behave during runtime.

Are there any misconceptions about hoisting in JavaScript?

A common misconception is that all variables and functions are hoisted equally. In reality, function declarations are fully hoisted with their definitions, allowing them to be called before their declaration, while variable declarations with var are hoisted without initialization.

Another misconception is that hoisting means variables are initialized with their values during hoisting. For var, only the declaration is hoisted, not the assignment, which can lead to undefined values if accessed prematurely. Understanding these nuances clarifies how JavaScript executes your code and improves your coding practices.

Related Articles

Ready to start learning? Individual Plans →Team Plans →
Discover More, Learn More
What Is AJAX (Asynchronous JavaScript and XML)? Learn how AJAX enables modern, responsive web applications by allowing seamless updates… What is JavaScript Module Loader? Discover how JavaScript module loaders improve application performance by dynamically managing dependencies… What is JavaScript Closure? Learn the concept of JavaScript closures and how they enable functions to… What is a JavaScript Engine? Discover how JavaScript engines power fast, interactive web applications by interpreting and… What Is (ISC)² CCSP (Certified Cloud Security Professional)? Discover the essentials of the Certified Cloud Security Professional credential and learn… What Is (ISC)² CSSLP (Certified Secure Software Lifecycle Professional)? Discover how earning the CSSLP certification can enhance your understanding of secure…