Hoisting
In short: before any code in a scope runs, the engine scans it and
registers every declared name — this is called hoisting. What a name
holds before its own line runs depends on how it was declared: a function
declaration is hoisted with its whole body, so it can be called early. var
is hoisted as undefined. let and const are hoisted but locked —
reading them before their line throws. That locked stretch, from the start
of the scope to the declaration's line, is called the temporal dead
zone.

Call a function before its declaration
Function declarations are hoisted whole, body included, so calling one "early" works exactly as if it had already been defined:
This prints hi. The engine registered the entire greet function before
line 1 ran, so by the time greet() executes, there is a real function
behind the name.
Read a var before its line, then a let
var is hoisted too, but only as undefined — the real value arrives when
its line runs. let is hoisted locked, in the temporal dead zone:
This prints undefined, not an error — the name existed early, just
without its value yet. Do the same with let:
This throws a ReferenceError before printing anything. title is
registered, but locked, until the line that declares it runs. Even
typeof title on the line above would throw — being in the dead zone gives
no protection.
A function stored in a var, called too early
This is where the two rules collide, and where the two different errors
matter. A function assigned to a var is not the same as a function
declaration:
This throws TypeError: play is not a function — not a ReferenceError.
play exists early (hoisted as var), but it holds undefined until its
assignment line runs, and calling undefined as a function is a
TypeError. Written as function play() { ... } instead, the whole
function would be registered from the start, and calling it early would
print go.
A ReferenceError means the name cannot be used yet, or does not exist at
all. A TypeError means the name exists but holds the wrong kind of value.
The simple habit that avoids both: declare before you use.
The code
play(); var play = function () { console.log("go"); };
Quick practice
What does console.log(x); var x = 5; log?
What does console.log(y); let y = 5; do?
What does greet(); function greet() { console.log("hi"); } do?
What does play(); var play = function () { console.log("go"); }; do?
What does let a = 1; { console.log(a); let a = 2; } do?
🎯 Your turn
Predict and explain the output of:
console.log(a); console.log(typeof b); console.log(c()); var a = 1; let b = 2; function c() { return 3; }
Write your explanation and submit it — there's no code to run here, just your reasoning.
