var, function declarations, arrow functions, and function expressions, uncover the true difference between undefined and not defined, visualize how the Call Stack works in real time, explore common mistakes, compare JavaScript's hoisting with other programming languages, and wrap up with real-world usage. By the end, you will be able to confidently answer any hoisting-related question — even in a technical interview.
What is Hoisting in JavaScript
Hoisting is a special behavior of JavaScript where you can access variables and functions even before they are declared or initialized in the code, without getting any error. In most other programming languages, trying to use something before it is declared would immediately result in a compilation or runtime error. JavaScript, however, treats things differently because of how its engine prepares the script before actually executing it.
In simple words, hoisting means that the JavaScript engine "knows" about all your variables and functions even before running a single line of your code. This happens because during the Memory Creation Phase of the Execution Context, the engine scans the entire script and reserves memory for every variable and function up front. This pre-scanning is what creates the illusion that your declarations have been "moved" to the top — but nothing actually gets moved. The memory is simply prepared in advance.
There is an important twist though — hoisting does not behave the same way for all kinds of declarations. A var variable gets hoisted with the placeholder undefined, a traditional function declaration gets hoisted along with its entire body, while arrow functions and function expressions behave like regular variables and get undefined. Understanding these subtle differences is what separates a beginner from a strong JavaScript developer.
Why Hoisting Exists in JavaScript
Hoisting is not an accidental quirk of JavaScript — it is a direct consequence of how the JavaScript engine creates its Execution Context in two clear phases. Before any of your code actually runs, the engine performs a Memory Creation Phase where it walks through the entire script and prepares memory slots for every variable and function it finds. This design gives JavaScript its forgiving, flexible nature.
This behavior was introduced so that JavaScript could remain simple and permissive for beginners while still offering powerful features under the hood. If hoisting did not exist, you would not be able to call a helper function that is defined later in the file, and organizing your code would become more rigid. Thanks to hoisting, you can structure your program in a readable, human-friendly way without worrying about the physical order of declarations.
Hoisting also makes it possible for the JavaScript engine to give meaningful default values like undefined instead of crashing your program. Rather than throwing a hard error for every early variable access, JavaScript uses this placeholder to indicate that the variable exists but has not yet received a value. This behavior is a foundation for many other JavaScript features such as closures, scope chains, and function expressions.
Internal Working: How Hoisting Actually Happens
Hoisting is not magic — it is just the Memory Creation Phase doing its job. The JavaScript engine executes every program in two strict phases inside an Execution Context. Understanding both phases is the real key to mastering hoisting.
In the first phase, known as the Memory Creation Phase, the engine scans through the entire script from top to bottom without executing a single line. It looks for all variable declarations and function declarations. Every variable declared with var gets a slot in the Variable Environment with the value undefined. Every function declaration (written as function name() {}) gets a slot where the entire function body is stored immediately, ready to be called.
In the second phase, known as the Code Execution Phase, the engine runs your code line by line. When it reaches an assignment like var x = 7, it replaces the undefined placeholder with the real value 7. When it reaches a function invocation, it creates a brand-new Execution Context for that function, executes the body, and then destroys that inner context once the function returns.
Memory Component
Code Component
Practical Examples of Hoisting
The best way to truly understand hoisting is to see it in action with real code samples. Let us explore several scenarios, one by one, and observe how JavaScript handles each case differently.
Example 1: Accessing a Function and Variable Before Declaration
getName();
console.log(x);
var x = 7;
function getName() {
console.log("Namaste JavaScript");
}
// Output:
// Namaste JavaScript
// undefined
In this example, we call getName() and log x before they are actually declared in the code. Surprisingly, JavaScript does not throw an error. Instead, the function executes perfectly and prints "Namaste JavaScript", while the variable x prints undefined. This happens because the Memory Creation Phase already allocated memory for both of them — the function got its full body, while the variable got the placeholder undefined.
Example 2: Accessing a Variable That Was Never Declared
console.log(x);
// No declaration of x anywhere
// Output:
// ReferenceError: x is not defined
This time, because there is no var x anywhere in the program, JavaScript never allocated any memory for x. When the engine tries to read its value, it finds nothing — and throws a ReferenceError: x is not defined. This is a completely different situation from the previous example, where x existed in memory as undefined.
Example 3: Logging a Function Reference Before Its Declaration
console.log(getName);
function getName() {
console.log("Namaste JavaScript");
}
// Output:
// Æ’ getName() { console.log("Namaste JavaScript"); }
Here we do not invoke the function — we just log the reference. JavaScript prints the entire function body because during the Memory Creation Phase, the complete function definition was already stored under the name getName. This shows clearly that function declarations are fully hoisted, not just their names.
Example 4: Arrow Function Behaves Like a Variable
getName();
var getName = () => {
console.log("Namaste JavaScript");
};
// Output:
// TypeError: getName is not a function
An arrow function assigned to a var behaves like a normal variable. During the Memory Creation Phase, only the variable name is allocated, and it gets the placeholder undefined. When we try to call getName() before its assignment line runs, JavaScript sees undefined, which is not a function — hence the TypeError.
Example 5: Function Expression Behaves Like a Variable
getName();
var getName = function () {
console.log("Namaste JavaScript");
};
// Output:
// TypeError: getName is not a function
Function expressions work exactly the same way as arrow functions in terms of hoisting. They are assigned to a variable, so only the variable is hoisted with undefined. The actual function body is attached only when the assignment line is executed. This is why mixing function expressions with early calls is a classic source of bugs.
Output Summary for Each Example
To make the behavior crystal clear, here is a side-by-side summary of what each example above prints in the browser console.
| Example | Declaration Type | Output |
|---|---|---|
| Function + var x | Function declaration + var | "Namaste JavaScript" and undefined |
| Access x without declaration | No declaration | ReferenceError: x is not defined |
| Log getName before declaration | Function declaration | Entire function body printed |
| Call arrow function early | var getName = () => {} | TypeError: getName is not a function |
| Call function expression early | var getName = function() {} | TypeError: getName is not a function |
Step-by-Step Execution: How the Engine Handles Hoisting
Let us trace the complete flow for the first example (function declaration plus var) so you can visualize exactly what happens at each stage of execution.
Visualizing the Call Stack During Hoisting
Every Execution Context lives on a data structure called the Call Stack. The Global Execution Context sits at the bottom, and every function call pushes a new Execution Context on top of it. When a function finishes, its context is popped off the stack.
This is the exact mechanism you can observe in the browser's DevTools Sources panel by adding breakpoints — the Call Stack panel shows each context being pushed and popped in real time, just like the demo in the video.
Common Mistakes Developers Make with Hoisting
Hoisting is powerful but often misunderstood. Let us look at the most common mistakes that even intermediate developers make.
Mistake 1: Believing Code is Physically Moved to the Top
A very common misconception is that hoisting literally moves your var and function declarations to the top of the file. This is not what happens. The actual source code stays in its original order. What is really happening is that memory is allocated during the Memory Creation Phase before execution begins. This is the answer interviewers are looking for when they ask about hoisting.
Mistake 2: Confusing undefined with Not Defined
These two look similar but mean very different things. undefined means the variable exists in memory but has not been given a value yet. Not defined means the variable was never declared anywhere, and accessing it throws a ReferenceError.
console.log(a); // Output: undefined
var a = 10;
console.log(b); // Output: ReferenceError: b is not defined
undefined confirms memory was allocated. Not defined means no memory slot exists at all. Never use them interchangeably.
Mistake 3: Expecting Arrow Functions to Be Fully Hoisted
Many developers assume all functions are treated equally by hoisting. This is false. Only traditional function declarations (function name() {}) are fully hoisted with their body. Arrow functions and function expressions assigned to var are only partially hoisted — just the variable name, with undefined as the value.
Mistake 4: Assuming let and const Behave Like var
Variables declared with let and const are also hoisted, but they are placed in a special state called the Temporal Dead Zone until their declaration line runs. Accessing them before their declaration throws a ReferenceError, which is very different from the undefined you get with var.
console.log(a); // Output: undefined (var is hoisted)
var a = 10;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(c); // ReferenceError: Cannot access 'c' before initialization
const c = 30;
Comparison: Hoisting Behavior for Different Declarations
Here is a complete side-by-side comparison of how each declaration type behaves during hoisting.
| Declaration Type | Memory Allocated? | Initial Value | Access Before Declaration |
|---|---|---|---|
| var | Yes | undefined | Returns undefined |
| Function Declaration | Yes | Entire function body | Works perfectly (can be called) |
| Arrow Function (via var) | Yes (variable only) | undefined | TypeError: not a function |
| Function Expression (via var) | Yes (variable only) | undefined | TypeError: not a function |
| let | Yes (in TDZ) | Uninitialized | ReferenceError (TDZ) |
| const | Yes (in TDZ) | Uninitialized | ReferenceError (TDZ) |
| Undeclared variable | No | Does not exist | ReferenceError: not defined |
Real-World Usage of Hoisting
Hoisting is not just an academic curiosity — it directly affects how you organize and debug JavaScript code every day. Here are some real-world scenarios where understanding it makes a tangible difference.
Scenario 1: Calling Helper Functions at the Top of a File
Thanks to hoisting of function declarations, you can structure your code so that the main logic appears at the top and helper functions are defined below. This improves readability without causing any errors.
var result = calculateTotal(100, 18);
console.log(result); // Output: 118
function calculateTotal(price, tax) {
return price + tax;
}
Scenario 2: Debugging Unexpected undefined Values
When you see undefined in the console instead of the value you expected, hoisting is almost always the explanation. Knowing that var declarations get hoisted with undefined saves hours of confusion.
console.log(userName); // Output: undefined (not an error!)
var userName = "Priya";
console.log(userName); // Output: Priya
Scenario 3: Understanding TypeError with Function Expressions
When a function expression throws a TypeError: undefined is not a function, it is almost always because the function was called before its assignment line. Recognizing this pattern helps you fix it immediately by moving the call after the declaration or switching to a function declaration.
Scenario 4: Writing Interview-Ready Explanations
In JavaScript interviews, explaining hoisting correctly signals a deep understanding of how the language works. Instead of saying "JavaScript moves declarations to the top", the correct answer is: "During the Memory Creation Phase of the Execution Context, the engine allocates memory for all variables and functions, placing undefined for var and the full body for function declarations — this creates the hoisting illusion."
Summary
Hoisting is one of the most unique and foundational concepts in JavaScript. It allows you to access variables and functions before they are declared in the source code, without errors, because the JavaScript engine allocates memory for all declarations during the Memory Creation Phase of the Execution Context — before any code actually runs.
The behavior, however, depends entirely on the type of declaration. A var variable is hoisted with the placeholder undefined. A traditional function declaration is fully hoisted along with its body, so it can be called anywhere in the file. Arrow functions and function expressions assigned to var behave like variables — only the name is hoisted with undefined, so calling them early throws a TypeError. Variables declared with let and const are hoisted into a Temporal Dead Zone and cannot be accessed until their declaration line runs.
The distinction between undefined and "not defined" is also critical. undefined means the variable exists in memory but has no value yet, while "not defined" means the variable was never declared at all and results in a ReferenceError. Together with the Call Stack demo, this gives you a complete picture of how JavaScript manages execution behind the scenes.
Mastering hoisting transforms the way you read, write, and debug JavaScript. You will no longer be surprised by undefined, confused by TypeError, or tripped up in interviews. Every behavior will feel predictable and elegant because you understand exactly what the engine is doing during the Memory Creation Phase and the Code Execution Phase.
| Concept | Key Takeaway |
|---|---|
| Hoisting | Access variables and functions before declaration without errors |
| Why it happens | Memory Creation Phase allocates memory before code runs |
| var | Hoisted with undefined as placeholder |
| Function Declaration | Fully hoisted with entire function body |
| Arrow / Function Expression | Only variable is hoisted, calling early gives TypeError |
| let / const | Hoisted but in Temporal Dead Zone (ReferenceError) |
| undefined vs not defined | undefined = exists in memory, not defined = never declared |
| Call Stack | Manages Execution Contexts with push and pop operations |