
Understanding Hoisting in JavaScript
Hoisting is one of JavaScript’s most fundamental—and arguably most confusing—concepts that every developer needs to master. It’s the mechanism that allows you to use variables and functions before they’re actually declared in your code, but it doesn’t work the way most people think it does. Understanding hoisting is crucial because it explains many of JavaScript’s seemingly bizarre behaviors, helps you avoid common bugs, and makes you write more predictable code. In this post, we’ll dive deep into how hoisting actually works under the hood, explore the differences between var, let, const, and function declarations, and look at practical examples that will make you a more confident JavaScript developer.
How Hoisting Works Under the Hood
Contrary to popular belief, JavaScript doesn’t actually “move” your declarations to the top of their scope. Instead, hoisting is about how the JavaScript engine processes your code in two phases: the compilation phase and the execution phase.
During the compilation phase, the engine scans through your code and allocates memory for all variable and function declarations. This is when the “hoisting” actually happens—the engine creates bindings for these identifiers in their respective scopes. Then, during execution, the engine runs your code line by line, assigning values and executing statements.
Here’s what actually happens with different types of declarations:
console.log(myVar); // undefined (not ReferenceError)
console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization
console.log(myFunction()); // "Hello from hoisted function!"
var myVar = "I'm a var";
let myLet = "I'm a let";
const myConst = "I'm a const";
function myFunction() {
return "Hello from hoisted function!";
}
The key difference is in how these declarations are initialized. var
declarations are hoisted and initialized with undefined
, while let
and const
are hoisted but remain uninitialized in what’s called the “Temporal Dead Zone” until their declaration is reached.
Variable Hoisting: var vs let vs const
Understanding the differences between these three declaration types is essential for writing predictable JavaScript code. Let’s break down each one:
Declaration Type | Hoisted | Initialized | Temporal Dead Zone | Scope |
---|---|---|---|---|
var | Yes | undefined | No | Function/Global |
let | Yes | Uninitialized | Yes | Block |
const | Yes | Uninitialized | Yes | Block |
Here’s a practical example showing these differences:
function demonstrateHoisting() {
console.log("var variable:", varVariable); // undefined
// console.log("let variable:", letVariable); // ReferenceError
// console.log("const variable:", constVariable); // ReferenceError
if (true) {
var varVariable = "I'm var";
let letVariable = "I'm let";
const constVariable = "I'm const";
}
console.log("var outside block:", varVariable); // "I'm var"
// console.log("let outside block:", letVariable); // ReferenceError
// console.log("const outside block:", constVariable); // ReferenceError
}
demonstrateHoisting();
Function Hoisting: Declarations vs Expressions
Function hoisting behavior varies significantly depending on how you declare your functions. This is where many developers get tripped up:
// Function declarations are fully hoisted
console.log(declaredFunction()); // "I'm declared!"
// Function expressions are not hoisted
console.log(expressedFunction()); // TypeError: expressedFunction is not a function
// Arrow functions are not hoisted
console.log(arrowFunction()); // TypeError: arrowFunction is not a function
function declaredFunction() {
return "I'm declared!";
}
var expressedFunction = function() {
return "I'm expressed!";
};
const arrowFunction = () => {
return "I'm an arrow!";
};
Function declarations are completely hoisted—both the identifier and the function body are available throughout their scope. However, function expressions and arrow functions follow the same hoisting rules as their variable declaration type.
Real-World Examples and Common Pitfalls
Let’s look at some real-world scenarios where hoisting causes unexpected behavior:
The Classic Loop Problem
// Problem: All buttons alert "3"
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log("var version:", i); // Always prints 3
}, 100);
}
// Solution 1: Use let instead of var
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log("let version:", i); // Prints 0, 1, 2
}, 100);
}
// Solution 2: Use IIFE with var
for (var i = 0; i < 3; i++) {
((index) => {
setTimeout(() => {
console.log("IIFE version:", index); // Prints 0, 1, 2
}, 100);
})(i);
}
Conditional Function Declarations
// Avoid this - behavior is unpredictable
if (true) {
function conditionalFunction() {
return "I might not work as expected";
}
}
// Better approach
let conditionalFunction;
if (true) {
conditionalFunction = function() {
return "I work predictably";
};
}
Best Practices and Modern JavaScript
Here are the key practices for dealing with hoisting in modern JavaScript development:
- Use const by default: Start with const for all declarations, then use let when you need reassignment. Avoid var unless you specifically need function scoping.
- Declare variables at the top of their scope: This makes the hoisting behavior explicit and improves code readability.
- Use function declarations for named functions: They’re fully hoisted and create cleaner, more readable code.
- Avoid temporal dead zone issues: Always declare let and const variables before using them.
- Use ESLint rules: Configure rules like no-use-before-define to catch hoisting-related issues.
Performance and Memory Considerations
Hoisting has minimal direct performance impact, but understanding it helps you write more efficient code:
Aspect | var | let/const | Impact |
---|---|---|---|
Memory allocation | Immediate (undefined) | Deferred until declaration | Minimal difference |
Scope creation | Function scope | Block scope | let/const can be more memory efficient |
Garbage collection | Later cleanup | Earlier cleanup | let/const advantage in loops |
Advanced Hoisting Scenarios
Here are some advanced scenarios that demonstrate hoisting’s complexity:
// Class hoisting
console.log(MyClass); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {
constructor(name) {
this.name = name;
}
}
// Import/export hoisting
console.log(importedFunction()); // Works! Imports are hoisted
import { importedFunction } from './module.js';
// Nested function hoisting
function outerFunction() {
console.log(innerFunction()); // "Inner function called"
function innerFunction() {
return "Inner function called";
}
if (false) {
function unreachableFunction() {
return "This is tricky";
}
}
// unreachableFunction behavior varies by JavaScript engine
}
Debugging Hoisting Issues
When debugging hoisting-related problems, these tools and techniques are invaluable:
- Browser DevTools: Use the debugger to inspect variable states during execution phases.
- ESLint configurations: Set up rules to catch common hoisting mistakes before runtime.
- TypeScript: Provides compile-time checking that can catch many hoisting-related errors.
- Babel transpilation: See how your modern JavaScript gets converted to older syntax.
Here’s a useful ESLint configuration for hoisting-related rules:
{
"rules": {
"no-use-before-define": ["error", {
"functions": false,
"classes": true,
"variables": true
}],
"prefer-const": "error",
"no-var": "error",
"block-scoped-var": "error"
}
}
For comprehensive information about JavaScript hoisting and variable declarations, check out the official MDN documentation on hoisting and the ECMAScript specification.
Understanding hoisting isn’t just about avoiding bugs—it’s about writing JavaScript that’s predictable, maintainable, and performs well. By mastering these concepts, you’ll be able to debug issues faster, write cleaner code, and better understand how JavaScript engines optimize your code execution.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.