Hoisting means declarations are processed before any code in that scope actually runs, but what gets hoisted — and how it's initialized — differs by declaration type.
console.log(a); // undefined — hoisted AND initialized to undefined
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
console.log(sayHi()); // works — function declarations are fully hoisted, body included
function sayHi() { return "hi"; }
console.log(sayBye()); // TypeError — sayBye is hoisted as a variable but not yet assigned
const sayBye = () => "bye";
var declarations are hoisted and initialized to undefined immediately — reading one before its assignment line gives undefined, not an error.let/const declarations are hoisted but left uninitialized. The span between the start of their scope and their actual declaration line is called the temporal dead zone (TDZ) — accessing the variable anywhere in that span throws a ReferenceError, which is generally more helpful than var's silent undefined.function foo() {}) are hoisted completely, body and all — you can call one before the line it's written on.let/const are not callable before their declaration, because the variable is in the TDZ even though the function itself, once assigned, works normally.