Before running your code, JavaScript scans it and registers declarations first. That is hoisting โ and it explains several "impossible" behaviours.
The three cases
sayHi(); // โ
works! function declarations hoist FULLY
function sayHi() { console.log("hi"); }
console.log(a); // undefined โ var hoists, value doesn't
var a = 5;
console.log(b); // โ ReferenceError โ TDZ
let b = 5;The temporal dead zone (TDZ)
let/const ARE hoisted โ but locked until their declaration line. Accessing them early throws instead of silently giving undefined. This is a feature: it catches bugs var would hide.
Function expressions do not hoist
greet(); // โ TypeError: greet is not a function
var greet = function() {}; // only `var greet` hoisted (as undefined)Practical rule: declare before use, use const/let, and hoisting becomes trivia you only need for interviews โ where it absolutely will appear.