Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
JavaScriptHoistingExecution ContextInterview PreparationFrontend Development

JavaScript Hoisting

Understand JavaScript hoisting — creation vs execution phase, var vs let vs const, function declarations vs expressions, and interactive memory visualizations.

Jul 3, 20268 min read
JavaScript Hoisting

Introduction

Hoisting is one of the most misunderstood ideas in JavaScript. Many developers picture the engine physically moving lines of code to the top of a file. That is not what happens.

Hoisting is the engine's behavior during the creation phase of an execution context: it scans declarations, allocates memory, and registers bindings before any statement runs. The source file order never changes — only the in-memory setup does.

This guide covers:

  1. The core concept — what hoisting really means
  2. Execution context phases — where hoisting happens
  3. Variable hoisting — var, let, and const
  4. Function hoisting — declarations vs expressions
  5. Best practices — how to write code that avoids hoisting pitfalls

Related reading: JavaScript Execution Context for the full runtime model behind hoisting.

Course Reference

Quick index

#Section
1What is hoisting?
2Where hoisting happens
↳ Hoisting memory scanner
3Variable hoisting
↳ var vs let vs const demo
4Function hoisting
↳ Function hoisting explorer
5Best practices

1. What is hoisting?

When JavaScript enters a new execution context, the engine performs two distinct phases before your program finishes:

PhaseWhat happens
CreationScan declarations, allocate memory, set up scope chain
ExecutionRun code line by line, assign values, invoke functions

Hoisting refers to the creation-phase behavior where the engine registers variable and function declarations in memory. It is a conceptual label — no code is physically moved to the top of the file.

javascript
console.log(greeting) // undefined (not ReferenceError)
var greeting = 'Hello'
 
sayHi() // 'Hi!' — works before the function line
 
function sayHi() {
  console.log('Hi!')
}

From reading the file top to bottom, greeting and sayHi appear to be used before they exist. In reality, the creation phase already registered both before line 1 executed.

Note

Hoisting happens inside an execution context — global scope, a function body, or a block (for let/const). Each context runs its own creation phase.

Interview Answer

Hoisting is the JavaScript engine allocating memory for declarations during the creation phase of an execution context, before any code executes. var and function declarations are initialized early; let and const are hoisted but remain in the temporal dead zone until their declaration line runs.

Back to index


2. Where hoisting happens

Hoisting is not a standalone feature — it is a consequence of how execution contexts boot up.

Creation phase checklist

  1. Create the variable environment and lexical environment
  2. Scan the scope for var, function, let, and const declarations
  3. Allocate memory for each binding
  4. Initialize var to undefined and store full function declarations
  5. Leave let/const uninitialized (temporal dead zone)
  6. Set up the outer scope reference and this binding

Execution phase checklist

  1. Run statements top to bottom
  2. Assign actual values to variables
  3. Execute function bodies when functions are invoked
javascript
// Creation phase registers: count → undefined, greet → full function
// Execution phase runs each line in order
 
console.log(typeof greet) // 'function'
console.log(typeof count) // 'undefined'
greet()
 
var count = 10
function greet() {
  return 'Hello'
}
let label = 'JS'

Interactive: hoisting memory scanner

Hoisting Memory Scanner

See what the engine registers in the creation phase before any line runs — no code physically moves to the top.

Hoisting = memory allocation during creation. The source file order stays the same.

hoisting.js

1console.log(typeof greet)
2console.log(typeof count)
3greet()
4var count = 10
5function greet() { return "Hello" }
6let label = 'JS'

Engine scans declarations and allocates memory — zero lines executed

countvar

undefined

greetfunction

ƒ greet() { return "Hello" }

labellet

⟨uninitialized⟩

Temporal Dead Zone

Tip

When debugging "used before defined" issues, ask: has the creation phase finished for this binding, and has the declaration line executed yet?

Back to index


3. Variable hoisting

Not all declarations behave the same during creation.

DeclarationHoisted?Initialized in creation?Access before declaration line
varYesundefinedReturns undefined — no error
letYesNo (TDZ)ReferenceError
constYesNo (TDZ)ReferenceError

var — initialized to undefined

javascript
console.log(x) // undefined
var x = 5
console.log(x) // 5

The binding exists before line 2 runs, but its value is undefined until assignment.

let and const — temporal dead zone

javascript
console.log(y) // ReferenceError
let y = 5
 
console.log(z) // ReferenceError
const z = 10

Both are registered in memory during creation, but accessing them before their declaration line throws a ReferenceError. This is the temporal dead zone (TDZ).

Warning

typeof undeclaredVar returns 'undefined' without throwing — but typeof on a TDZ let binding still throws ReferenceError. Do not use typeof as a TDZ safety check.

Interactive: variable hoisting compare

Variable Hoisting Compare

Toggle var vs let vs const and see what happens when you access a variable before its declaration line.

1console.log(x)
2var x = 5
3console.log(x)

Memory

x → undefined

Console result

undefined (no error)

All three are hoisted into memory during creation. Only var is initialized to undefined. let and const stay in the temporal dead zone until their declaration line executes.

Interview Answer

var is hoisted and initialized to undefined, so reading it before the declaration line returns undefined. let and const are hoisted but not initialized — they sit in the TDZ until their declaration executes, and early access throws ReferenceError. All three are hoisted; the difference is initialization timing.

Back to index


4. Function hoisting

Functions follow different rules depending on how they are defined.

Function declarations — fully hoisted

javascript
greet() // 'Hello'
 
function greet() {
  return 'Hello'
}

The entire function — name and body — is stored in memory during creation. You can call it before the declaration line.

Function expressions — variable hoisting rules apply

javascript
sayHi() // TypeError: sayHi is not a function
 
var sayHi = function () {
  return 'Hello'
}

Here sayHi is a var. Creation phase sets it to undefined. Calling it before assignment throws TypeError because undefined is not callable.

javascript
sayHi() // ReferenceError
 
const sayHi = () => 'Hello'

With const or let, the binding is in the TDZ — you get ReferenceError, not TypeError.

Named function expressions

javascript
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1)
}
// `fact` is only visible inside the function body

The outer name follows variable hoisting rules; the inner fact name is local to the function.

Interactive: function hoisting explorer

Function Hoisting Explorer

Compare function declarations vs expressions — see what is stored in memory and what happens when you call before the definition line.

1sayHi()
2function sayHi() {
3 return 'Hello'
4}

Creation phase memory

sayHi → ƒ sayHi() { return "Hello" } (full body stored)

sayHi() on line 1

'Hello'

Function declarations are fully hoisted. The entire function — name and body — is available before the declaration line runs.

TypeCreation phaseCall before line
DeclarationFull function storedWorks
Expr + varVariable → undefinedTypeError
Expr + const/letTDZReferenceError

Performance

Prefer function declarations at module scope when you need hoisting for mutual recursion. For callbacks and assignments, use arrow functions or expressions — but declare them before use to avoid relying on hoisting behavior.

Back to index


5. Best practices

Hoisting explains odd behavior — it should not be something you depend on in production code.

Declare before use

javascript
// Avoid
console.log(total)
var total = calculateTotal()
 
// Prefer
function calculateTotal() {
  return 100
}
const total = calculateTotal()
console.log(total)

Use let and const instead of var

var hoisting to undefined silently hides bugs. Block-scoped let/const with TDZ catch mistakes at the declaration line.

javascript
// var leaks and hoists oddly in blocks
for (var i = 0; i < 3; i++) {}
console.log(i) // 3
 
// let stays block-scoped
for (let j = 0; j < 3; j++) {}
// console.log(j) // ReferenceError

Put function declarations before calls (or use expressions after setup)

javascript
// Clear intent — no reliance on hoisting
function init() {
  bindEvents()
  fetchData()
}
 
function bindEvents() {
  /* ... */
}
function fetchData() {
  /* ... */
}
 
init()

Understand execution context for debugging

When you see unexpected undefined, TDZ errors, or "is not a function", trace:

  1. Which execution context is active?
  2. Has the creation phase registered this binding?
  3. Has the declaration line executed yet?

Tip

Enable ESLint rules like no-use-before-define and @typescript-eslint/no-use-before-define to catch hoisting footguns at lint time.

Back to index


Conclusion

Hoisting is not magic and it is not code rearrangement. It is the engine preparing memory during the creation phase of each execution context.

Key takeaways:

  • Hoisting = creation-phase memory allocation for declarations
  • var → hoisted and initialized to undefined
  • let / const → hoisted but TDZ until the declaration line
  • Function declarations → fully hoisted with body
  • Function expressions → follow the variable rules of their binding
  • Best practice → declare before use; prefer let/const over var

Pair this guide with JavaScript Execution Context for the full picture of how code actually runs.


Have questions about hoisting or the temporal dead zone? Reach out via the contact page.

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime