← all labs  ·  ← prev lab  ·  next lab →

Foundation Lab 06

Creation Phase vs Execution Phase

Lab 05 showed a context gets pushed and popped. Now we open one up: it's actually built in two separate passes, and that's what makes "hoisting" look the way it does.

What this lab produces: no files, no commit — a pure concept lab. You'll run one short snippet in a console or with node.
Concept type: Mostly JavaScript-Specific
▶
EXECUTION 10 of 10
◎
NAMES & SCOPE 2 of 10

Hoisting sits at the intersection of how code executes and where names become visible.

Mental Models

Two passes over the same code A code block is scanned once by a registration pass, then again by an execution pass. pass 1 register pass 2 run

Mental Model 1 of 5Two Passes, Same Code

var registers empty, a function declaration registers full Two boxes created in pass 1: one empty labeled var, one already containing a function. undefined var x function function f(){}

Mental Model 2 of 5Registered Empty vs Registered Full

Code does not physically move Lines of code stay in their original order; only a separate registration ledger is built early. source order — unchanged ✓

Mental Model 3 of 5Hoisting Is Not Teleportation

An analogy: setting up a restaurant before it opens A closed restaurant with staff and menu already set up behind the door, versus the open sign and customers being served. staff hired menu printed CLOSED customers served OPEN

Mental Model 4 of 5An Analogy: Setup Before Opening

Misconception: hoisting is a feature to lean on, not a side effect to understand A red X over a hand deliberately reaching for something out of order, a green check over a hand reading top to bottom. ✕ rely on early access ✓ understand, don't lean on it

Mental Model 5 of 5Misconception: Hoisting Isn't a Feature to Use

Learning Objectives

  1. Objective 1 of 4 — I can describe the creation phase and execution phase as two distinct passes over the same code, not one single pass.
  2. Objective 2 of 4 — I can predict what a var-declared variable holds, and what calling a function declaration returns, when either is used before its own line in the file runs.
  3. Objective 3 of 4 — I can explain why converting a function declaration into a function expression changes what happens when it's called early.
  4. Objective 4 of 4 — I can explain "hoisting" accurately, without describing it as code physically moving.

Pre-Lab Quiz

Answer from intuition first.

Question 1 of 5You log a var-declared variable on the line before its declaration and assignment. What happens?Open question

Question 2 of 5You call a function declaration on the line before its definition appears in the file. What happens?Open question

Question 3 of 5Does JavaScript actually reposition your declarations to the top of the file before running it?Open question

Question 4 of 5Two function declarations, A and B, each call each other, and B is written above A in the file even though A is called first. Does this work?Open question

Question 5 of 5var x = 1; appears twice in the same scope (var x = 1; ... var x = 2;). Does the second one cause an error?Open question


The Concept

Building an execution context is a two-step process

When the engine builds an execution context (Lab 05), it doesn't immediately start running your code top to bottom. It first does a full pass over that context called the creation phase, registering every var and function declaration it finds — before a single line has actually executed. Only after that's done does the execution phase begin, running your code top to bottom for real.

Why register everything up front, in a separate pass?

Consider two functions that need to call each other — function A calls function B somewhere in its body, and function B calls function A somewhere in its. If the engine only recognized a function's existence once execution physically reached its definition line, this kind of mutual reference would be nearly impossible to write, since whichever one is defined second wouldn't exist yet when the first one's definition is being read. Registering every function declaration's full value up front, before any code runs, sidesteps that problem entirely — by the time any code executes, every function declaration in that scope already exists, regardless of the order they were written in.

function isEven(n) {
  if (n === 0) return true;
  return isOdd(n - 1); // isOdd is used here...
}

function isOdd(n) {   // ...but only defined below
  if (n === 0) return false;
  return isEven(n - 1);
}

What gets registered, and how

  1. Every var name is registered and set to undefined — a slot exists, but empty.
  2. Every function declaration is registered with its entire function already as the value — fully ready to call.
  3. The execution phase then runs top to bottom, assigning real values to vars as their lines are reached.

This two-phase process is what people call "hoisting" — but nothing physically moves, as the third mental-model diagram above shows. The registration was always going to happen first; it only looks like the declaration relocated.


Lab

Primary objective: observe the split between "registered" and "assigned" directly, for both a var and a function declaration.

You'll need: a browser console or node — no project, no files.

Lab 1 of 1 — Watch the two-phase split happen

Task

Use each name before its own line in the file runs, and see which one is "empty but safe" versus "fully ready."

Instructions

Step 1 of 4

// Paste this into a scratch file or console. Read it — do not run it yet.

console.log(nickname);
announce();

var nickname = "Milo";

function announce() {
  console.log("hi, I'm " + nickname);
}

This step is complete when: you've predicted what each of the first two lines actually does when it runs.

Step 2 of 4

Predict what console.log(nickname) prints, and what calling announce() does at that point in the file.

This step is complete when: you've run the snippet yourself and it matches the revealed output.

Step 3 of 4

// Edit the file yourself. Don't paste finished code — type it from these comments.

// Change the function declaration to a function expression:
// replace "function announce() {...}" with
// "var announce = function () {...}", keeping the same body.

// Leave the two calls at the top of the file exactly where they are.

This step is complete when: announce is now a var holding a function expression, not a function declaration.

Step 4 of 4

Predict: does announce() still work at the top of the file now, or does something change? Run it and compare, then answer this comprehension check: in your own words, why does a function declaration survive being called early, while a function expression assigned to a var does not?


Comprehension Checklist

  • I can describe the creation phase and execution phase as two separate passes, not one.
  • I know a var registers as undefined, while a function declaration registers with its full value.
  • I know "hoisting" doesn't move code — it's just that registration happens before execution.
  • I can explain why two mutually-calling function declarations work regardless of which one is written first.

Post-Lab Quiz

These are different questions from the pre-lab quiz — they test what the experiment should have taught you, not the same questions again.

Question 1 of 5console.log(typeof mystery); var mystery = 5; — what does this log? (Tie this back to Lab 03's typeof.)Open question

Question 2 of 5Why did converting announce from a function declaration to a var-assigned function expression change the outcome?Open question

Question 3 of 5Does JavaScript literally move declarations to the top of the file at runtime?Open question

Question 4 of 5Would the isEven/isOdd mutual-recursion example from the Concept section work if isOdd were rewritten as a var-assigned function expression instead of a declaration?Open question

Question 5 of 5Which happens first: the creation phase finishing for the whole file's top-level code, or the very first line of that code actually executing?Open question


Reflection Questions

Write your own answer first — the blurred text is one possible answer, not the only correct one.

Reflection 1 of 3. Even though calling a function declaration before its line "works," why might relying on that be a risky habit?

Reflection 2 of 3. How does the two-phase model explain why declaring the same var name twice in one scope doesn't cause an error?

Reflection 3 of 3. Before this lab, would you have predicted that the mutual-recursion example (isEven/isOdd) could work at all, given that each function references the other before it's "been defined" in file order? What does that reveal about how you were thinking about "defined"?


What Breaks If This Understanding Is Wrong?

Without the two-phase model, the Temporal Dead Zone (Lab 07) looks like an arbitrary rule instead of a direct consequence of it — let/const go through the same creation phase, just registered in a different, inaccessible state.

What JavaScript Concept Was Learned Today?

Every execution context is built in two passes — a creation phase that registers names first, then an execution phase that actually runs the code — and that's the real mechanism behind what's casually called "hoisting."

Relates to: ▶ Execution (10 of 10) + ◎ Names & Scope (2 of 10) — hoisting is what happens when "how code runs" and "when a name becomes visible" meet.


Next

Lab 07 asks what happens when let and const go through this same creation phase instead of var — and shows they land in a much stricter, locked state called the Temporal Dead Zone, directly explained by the two-phase model you just built.

← All labs  ·  Lab 07 — var, let, const & the Temporal Dead Zone →