Function Hoisting: Declarations vs Expressions

Med

Function declarations and function expressions behave very differently with hoisting. Function declarations are fully hoisted (both name and body), allowing you to call them before their definition. Function expressions follow variable hoisting rules - var function expressions are hoisted as undefined, while let/const function expressions are in the TDZ.

Interactive Visualization

CodeCreation Phase
1console.log(x); // ???
2var x = 5;
3console.log(x); // ???
Variable Environment
xundefinedhoisted
Output
Global EC created. JS scans for declarations and hoists var x to the top with value undefined
1 / 4
Hoisted (undefined)
TDZ (cannot access)
Initialized

Key Points

  • Function declarations: fully hoisted (name + body)
  • Function expressions: follow variable hoisting rules
  • var function expressions: hoisted as undefined
  • let/const function expressions: TDZ error if called early
  • Arrow functions are expressions, not declarations
  • Named function expressions are different from declarations

Code Examples

Function Declaration - Fully Hoisted

// Can call BEFORE definition!
sayHello(); // "Hello!" - Works!

function sayHello() {
  console.log("Hello!");
}

// The entire function is hoisted to the top
// This is why it works!

Function declarations are fully hoisted - both the name and the function body move to the top

Function Expression with var

// console.log(sayHi); // undefined
// sayHi();              // ❌ TypeError: sayHi is not a function

var sayHi = function() {
  console.log("Hi!");
};

sayHi(); // "Hi!" - Works after assignment

// JavaScript sees:
// var sayHi;           // Hoisted declaration
// console.log(sayHi);  // undefined
// sayHi();             // undefined is not callable!
// sayHi = function(){}; // Assignment happens here

var function expressions are hoisted as undefined, so calling them before assignment throws TypeError

Function Expression with let

// sayHey(); // ❌ ReferenceError! TDZ

let sayHey = function() {
  console.log("Hey!");
};

sayHey(); // "Hey!"

// Same TDZ behavior as any let variable

let function expressions are in TDZ until the declaration line

Arrow Functions - Always Expressions

// arrowFunc(); // ❌ ReferenceError! TDZ

const arrowFunc = () => {
  console.log("Arrow!");
};

arrowFunc(); // "Arrow!"

// Arrow functions are always expressions
// They follow variable hoisting rules

Arrow functions are always function expressions, never declarations

Named Function Expression

const factorial = function fact(n) {
  // 'fact' is available inside for recursion
  return n <= 1 ? 1 : n * fact(n - 1);
};

console.log(factorial(5)); // 120
// console.log(fact);      // ❌ Error! 'fact' is not available outside

// The name 'fact' is only available inside the function

Named function expressions allow self-reference but the name is not available outside

Common Mistakes

  • Calling function expressions before their definition
  • Thinking arrow functions are hoisted like declarations
  • Using function declarations when conditional creation is needed
  • Expecting named function expression names to be available outside

Interview Tips

  • Clearly distinguish declarations vs expressions
  • Remember: function declarations = fully hoisted, expressions = variable hoisting
  • Arrow functions are always expressions
  • Named function expressions help with debugging and recursion