Hoisting
This lesson zooms in on a behavior people often call hoisting: you can run code that
references var bindings and function declarations that appear later in the source
file, without a parse error. The engine is not “moving lines to the top” in your editor —
it is using the two-phase model you already met in
Execution context and
Call stack & phases.
The examples below use var and function declarations so the same mental model
matches the classic “creation phase + code execution” story. let and const have a
separate Temporal Dead Zone story; you can look that up after this page.
What hoisting is
Hoisting (informal name) is what you observe when the memory-creation phase of an execution context has already registered names before any line of the script’s execution phase runs.
- The proper interview answer: bindings are set up in the first phase; assignments and calls run in the second phase. That is the mechanism — not a literal cut-and-paste of your file.
Creation phase & bindings
For a global script (a classic top-level var and function flow):
var x: the namexexists in the environment for that context, and its value starts as the placeholderundefineduntil an assignment runs in the code phase.function getName() { ... }: the namegetNameis bound to the actual function value (the callable) during creation, not toundefined.
So before the first line of your program executes, the engine can already find getName as
a function, while x is “here but empty” — that is undefined.
Phase 1 → 2: var + function declaration
End of the memory-creation phase: the engine has registered `var x` and the `function` declaration. `x` is `undefined`; `getName` already holds the full callable — before any line of this script has run.
The diagram uses reordered lines to match the “invoke first, declare later” demo: the
important point is the state of memory at the end of creation, not the order of lines
in your real file. If you place getName() and console.log(x) above var x and
function getName, the same creation-phase table applies, then the execution phase runs
top to bottom.
Typical output when the call and log run before the assignment:
- The log from inside
getName(e.g. “Namaste JavaScript”). - Then
console.log(x)printsundefined, becausevar x = 7has not run yet. - After
var x = 7runs,xis7for any later use.
undefined vs not defined
undefined (the value) means: a binding exists in the current environment, but no
value has been assigned yet (for var) or the variable is in an allowed but “empty” state.
ReferenceError: x is not defined (what engines say for a missing binding) means:
there is no x in the relevant scope at all — for example, you removed the var x line and never declared x in any other way. That is not the same as undefined.
In short: undefined = name exists; not defined = name does not exist in scope.
// Binding exists, no assignment yet → undefined
var x
console.log(x) // undefined
// No binding at all → ReferenceError
console.log(missing) // ReferenceError in strict/global lookup rules
No binding for x
If `var x` never appears in the program, the engine does not create a global binding for `x`. After creation, only `getName` exists here.
Logging vs calling
With a function declaration name that was fully set up in the creation phase:
console.log(getName)logs the function value itself (its source or engine-specific representation), becausegetNamealready resolves to the function.console.log(x)with onlyvar xabove it logsundefined, for the same reason as any other pre-assignment use of thatvar.
So both names are “visible” after creation, but one holds a function object and the
other holds the undefined placeholder until the assignment line runs.
Logging: function value vs undefined
Right after the creation phase, both names exist in global memory: `getName` is the function, `x` is `undefined`. The snippet below only logs — it does not invoke `getName` yet.
Declarations vs expressions
A function expression (and an arrow function assigned to a variable) is a variable
binding first: it behaves like var / let / const for when the name points at a
callable.
function getName() { }— the declaration form: the name is fully wired to the function in the creation phase of that scope.var getName = () => { }orvar getName = function () { }— the name is created like avarin creation (undefinedfirst), and only after the assignment line runs doesgetNamebecome a function.
If you call getName() before that assignment line, the binding is still undefined
in the var case — the call fails with a “not a function” style error. With let /
const, you hit the temporal dead zone instead, so you get a ReferenceError for
the name even before you talk about it being a function. This page stays with var
for the parallel to the undefined table.
Function expressions and arrow functions attached to a property follow the same
assignment-time story: the property is not a function declaration, so you do not get
the “full function in creation phase on that name” effect.
Function expression: like a var at creation
With an arrow (or a function expression assigned to `var` / `const`), the name is treated like an ordinary variable in creation: `getName` starts as `undefined` until the assignment line runs.
Recap
- Hoisting in conversation usually means: names are registered in the creation phase before the code execution phase runs line by line.
var: name exists, valueundefineduntil the assignment.functiondeclaration: name is the function in creation phase.- Function expression / arrow assigned to a name: the name starts like a variable (for
var,undefineduntil the assignment line; forlet/const, TDZ applies). undefined= binding is there; not defined (ReferenceError) = no binding in scope.
✦Before: Call stack & phases — creation vs execution, invocations, stack.
Next: Event loop — how async and tasks interact once synchronous code finishes.