Hoisting in JavaScript: The Hidden Mechanism Every Developer Must Understand
Ever seen JavaScript call a function at the very top while its definition sits at the bottom—yet it still runs? Or accessed a variable before declaration and got undefined instead of an error? That’s hoisting. Understanding it not only helps with technical interviews but is also key to how the JavaScript engine works under the hood—before any line of code executes.

Trung Vũ Hoàng
Author
1. What Exactly Is Hoisting?
Many resources describe hoisting as “moving declarations to the top of the file.” It’s an easy way to think about it, but not entirely accurate—no lines of code are actually moved. What really happens is: during the Creation Phase, the JavaScript engine scans the source, finds all variable and function declarations, and allocates memory for them—before execution starts.
This creates the “illusion” that every declaration is available from the start of its scope. Without a clear understanding of this, you can run into subtle logic bugs—especially in multi-contributor codebases with mixed coding styles.
2. var, let, const — How Does Hoisting Behave Differently?
Here’s the key distinction:
var — Hoisted and initialized to undefined
When you declare with var, the variable is hoisted and assigned the default value undefined from the start. You can access it before the declaration line without a crash—but you’ll get undefined, which can be confusing.
let and const — Hoisted but in the Temporal Dead Zone (TDZ)
let and const (ES6+) are also hoisted, but they are not initialized. They sit in the Temporal Dead Zone (TDZ)—the period from when the block starts until the declaration line executes. Accessing a variable during this period throws a ReferenceError immediately.
This is why let and const are considered “safer”—they force you to declare before use, making code clearer and more readable.
3. Hoisting with Functions: Function Declaration vs Expression
Function Declaration — Fully hoisted
Functions declared with the traditional syntax (function functionName() {}) are hoisted entirely—both the name and the function body are allocated up front. You can call the function anywhere in the scope, even before its definition line.
Function Expression — Follows the variable’s rules
If you assign a function to a variable (const fn = function() {} or var fn = function() {}), the variable’s hoisting rules apply. With var, the variable is undefined, and calling it early yields “is not a function”. With const, you’ll get a ReferenceError.
4. Why Does JavaScript Have This Mechanism?
Hoisting isn’t an accidental quirk—it has a clear design rationale. Most importantly, it enables mutual recursion. If function A calls function B and B calls A, both must “know” about each other’s existence. Hoisting allows that regardless of declaration order in the file.
It also reflects JavaScript’s original philosophy: try to execute rather than fail immediately on small issues. In modern development, however, we prefer discipline and explicitness over this kind of error-prone “flexibility.”
5. Real-World Examples and Pitfalls
console.log(name); // undefined — not an error!
var name = "JavaScript";
console.log(name); // "JavaScript"The engine effectively does this: var name; is hoisted first (with the default value undefined), then the first console.log runs and sees undefined, and only afterward does the assignment execute. Replacing var with let triggers a ReferenceError on the first line.
Another common trap: var has no block scope. A variable declared with var inside an if or for “leaks” out of the block and exists at function or global scope—fuel for countless bugs in asynchronous loops.
6. Best Practices — Work Well with Hoisting
Forget var; use let and const: This is rule number one. Use
constwhen you don’t need reassignment, andletfor the rest.Declare variables at the top of the scope: Even with
let/const, declaring variables near the top of a function helps readers grasp all the “ingredients” of the logic.Avoid naming variables the same as functions: This is a source of confusing, hard-to-reproduce bugs.
Enable Strict Mode: Add
"use strict";at the top of the file to prevent JavaScript from implicitly creating globals when you forget a declaration keyword.
Connection to Closures: Hoisting prepares the Lexical Environment—the context a Closure later “captures” and remembers. Understanding hoisting is a natural stepping stone to deeply understanding closures.
7. Summary
Hoisting isn’t a design flaw—it’s an intentional feature. Once you understand the Creation Phase and Execution Phase, you grasp how JavaScript actually operates—beyond surface-level explanations.
Clean code isn’t about clever tricks. It’s code any teammate can read and understand immediately. Use your knowledge of hoisting to write clearer code—not to hide complexity.
Frequently Asked Questions
Bài viết liên quan

Advanced Promises: all, allSettled, race, any — When to Use Which?
If you only use sequential async/await, your app is wasting performance potential — each request has to wait for the previous one to finish before it can start. Promise’s static methods (all, allSettled, race, any) let you orchestrate multiple async tasks in parallel using the strategy that fits each problem.

What Is Layer 2? Understanding Rollups, State Channels, and Sidechains
An easy-to-follow explanation of Layer 2, with a deep dive into Rollups, State Channels, and Sidechains: how they work, pros and cons, security risks, and how to choose the right option for both users and development teams.

What Is an NFT? A Clear Guide to ERC-721 and Real-World Applications
This article explains NFTs (Non-Fungible Tokens) in a way that’s easy to follow yet detailed: why NFTs are “non-fungible,” how the ERC-721 standard works on Ethereum, how NFT data is stored, and practical use cases beyond collecting images.