cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Hoisting & the temporal dead zone

Understand why function declarations work before their line, why var reads as undefined, why let, const, and class have a temporal dead zone, and how undefined differs from not defined.

By the end you can
  • 01
    Replay creation vs executionSee hoisting as the creation phase of an execution context.
  • 02
    Predict early readsTell function declarations, var, let, const, class, and missing names apart.
  • 03
    Fix timing bugsReorder code and declarations so TDZ and early-call errors disappear.

Hoisting is the creation phase

STEP THROUGH

In the Execution contexts lesson, you met two phases: a creation phase, then an execution phase. Hoisting is the visible result of that creation phase. Before the first line executes, JavaScript scans the scope and prepares declared names.

The tricky part is that different declarations arrive in different conditions. Function declarations arrive ready. var arrives with undefined. let, const, and class names exist, but they are uninitialized until execution reaches their declaration line. That uninitialized window is the temporal dead zone, or TDZ.

The short version

Hoisting does not move your code. It means declarations are registered during creation. The result depends on the declaration kind: ready function, var as undefined, or a TDZ binding that throws if read too soon.

Real-life analogyHoisting is attendance before class

Imagine a teacher taking attendance before class starts. Some students are ready with notebooks open. Some are in the room but empty-handed. Some are present but asleep. And some names are not on the roster at all. Hoisting is JavaScript’s attendance pass before execution begins.

In real life: The teacher reads the roster before class starts
In JavaScript: Creation phase registers declared names before line 1
In real life: A prepared student can answer right away
In JavaScript: Function declarations are callable immediately
In real life: A student is present but empty-handed
In JavaScript: var exists with value undefined
In real life: A student is present but asleep
In JavaScript: let, const, and class are in the TDZ
In real life: A name not on the roster
In JavaScript: An undeclared name is not defined

Where the analogy stops: A teacher can wake a student gently. JavaScript cannot: reading a TDZ binding throws immediately. Also, the engine does not literally keep a classroom roster; this is a mental model for the creation phase.

Try the replay. Line 2 can read a var, a let, or a missing name. The outcomes are real JavaScript behavior, and the tests for this lesson run the snippets in Node’s vm to prove them.

Take attendance before line 1
Step 0 of 8Ready
Your turn: follow the blue line

Creation phase is JavaScript taking attendance before line 1. Change line 2, then predict whether it prints a value or throws.

Running in
  1. script
Next: line 5
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(greet());console.log(score);var score = 7;let status = "ready";console.log(status);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what line 2 reads early

Changing line 2 starts a fresh replay. Predict whether it prints, throws a TDZ error, or says the name is missing.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Function hoisting: declarations arrive ready

A function declaration is the friendliest form for early calls. During creation, JavaScript creates the function object and stores it in the function’s name. That is why this works:

A function declaration can be called earlyPop out in the code editor (opens in a new tab)JavaScript
console.log(double(4));
function double(n) {
  return n * 2;
}

The body is not executed during creation. It is merely attached to the name. Execution still starts at line 1, calls double, enters the function body, and returns 8.

Block functions in strict code

In modern strict-mode code, a function declaration inside a block is block-scoped. Avoid relying on old sloppy-mode block-function behavior; it exists mostly for web compatibility and can differ between environments. This course uses strict, modern patterns.

This is useful for readability. Many teams put the main story first and helper declarations below it. That works when the helpers are declarations, not expressions assigned to variables.

var hoisting: known, but undefined

CONTRAST

var is older than let and const. It is hoisted and initialized to undefined during creation. The assignment stays exactly where you wrote it.

The assignment is not hoistedPop out in the code editor (opens in a new tab)JavaScript
console.log(score); // undefined
var score = 7;
console.log(score); // 7

This is one reason modern JavaScript usually prefers Variables: let, const & var. var can hide bugs by producing a value instead of failing fast. If you expected 7, undefined is still a bug; it is just a quieter bug.

Real-life analogyA reserved parking space

var is like a reserved parking space. The name is already in the lot, but until the car arrives, the space is empty. Reading it early tells you the space exists and is empty: undefined.

In real life: The sign says Ada has a space
In JavaScript: The var name exists during creation
In real life: The car has not arrived
In JavaScript: The value is undefined before assignment
In real life: The car arrives later
In JavaScript: The assignment line stores the real value

Where the analogy stops: A parking sign is visible to people. JavaScript bindings are not visible objects; they are how scope lookup resolves names.

The TDZ for let, const & class

STEP THROUGH

let, const, and class declarations are hoisted too, but not initialized. From the start of the scope until execution reaches the declaration line, the binding is in the temporal dead zone. “Temporal” means it is about time, not simply text position.

TDZ reads throwPop out in the code editor (opens in a new tab)JavaScript
console.log(theme); // ReferenceError
let theme = "dark";

console.log(typeof Widget); // ReferenceError
class Widget {}

Class declarations behave like let here: the name is known, but reading it before the class declaration runs throws. The class itself is covered in Class basics later; for now, remember that class declarations are TDZ bindings.

TDZ is about time, not text position
Step 0 of 5Ready
Your turn: follow the blue line

TDZ is about time, not text position. Step through the call that is written early but happens late.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  return secret;}let secret = "initialized";console.log(readLater());
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Fun fact: typeof is not always safe

typeof notDeclared returns "undefined" because no binding exists. But typeof later before let later throws, because a binding does exist and is in the TDZ. That is one of the sharpest differences between “undeclared” and “not initialized yet.”

Default parameters can have a TDZ too. Parameters are initialized left to right, so function f(a = b, b = 1) reads b before b has been initialized. Swap them to function f(b = 1, a = b) and it works.

Function expressions and arrows depend on the variable

INTERACTIVE

Function declarations are special. A function expression or arrow function is a value created when its assignment line executes. The variable that will hold that value follows the variable’s own hoisting rules.

Declarations, expressions, and arrows before assignment
Step 0 of 4Ready
Your turn: follow the blue line

Choose which form creates double, then predict line 1: value, TypeError, or ReferenceError.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(double(4));  return n * 2;}
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the shape of double

Only the declaration arrives with a callable function. The other two prove different early-read failures.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The punchline: var double = function... gives you an early undefined, so calling it throws TypeError. const double = () => ... gives you a TDZ binding, so even reading it for the call throws ReferenceError. The earlier Function expressions & callbacks lesson introduced the syntax; this lesson explains why the timing differs.

undefined vs not defined

These phrases sound similar but mean different things. undefined is a real JavaScript value. “Not defined” is part of a ReferenceError message saying scope lookup did not find a binding.

Early-read outcomes
CaseEarly read resultWhy
Function declarationCallable functionThe binding and function body are prepared during creation.
var score before assignmentundefinedThe binding is created and initialized to undefined; assignment happens later.
Declared but not assignedundefinedA real binding exists, but no value has been stored yet.
let, const, or class before initializationReferenceErrorThe binding exists but is uninitialized: the TDZ.
Never declaredReferenceError: name is not definedNo binding was found in the scope chain.
var fn = function... called earlyTypeErrorThe name exists as undefined; calling undefined is not possible.

Sort the cases by what happens. This is the quickest way to check if you are distinguishing ReferenceError from TypeError and a real undefined value.

undefined, ReferenceError, or TypeError?
  • console.log(score); var score = 1;
  • console.log(add(1, 2)); function add(a, b) { return a + b; }
  • console.log(score); let score = 1;
  • run(); const run = () => 'go';
  • run(); var run = function () {};
  • console.log(notDeclared);
Try it yourself
0 of 6 correct

Place each early-read snippet into the bucket that matches its real outcome.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

Hoisting shows up whenever code is organized for humans. You might put helper declarations below the main flow, split setup across a file, or debug a stack trace that points to a line before a declaration.

Readable when helpers are declarationsPop out in the code editor (opens in a new tab)JavaScript
console.log(renderGreeting("Ada"));

function renderGreeting(name) {
  return "Hello, " + name;
}

Best practice is still simple: declare variables near the top of the scope where you use them, initialize const immediately, and do not depend on early var reads. Hoisting knowledge is not a license to be clever; it is a debugging tool.

Common misconceptions

“Hoisting moves my code to the top.”

No. Source order is unchanged. Creation prepares bindings; execution still runs statements in order.

“Only functions are hoisted.”

Function declarations are the easiest to see, but var, let, const, and class names are all registered during creation.

“TDZ means the line is physically above the declaration.”

TDZ is about when execution reads the binding. A function written above a let is fine if it is called after the let line runs.

“typeof never throws.”

It is safe for undeclared names, but not for TDZ bindings.

“ReferenceError and TypeError are interchangeable.”

A ReferenceError means the name lookup failed or hit TDZ. A TypeError means a value was found, but the operation is invalid, such as calling undefined.

Practice: predict and fix timing bugs

5 EXERCISES
Exercise 1 · Warm-upPredict a var read

Predict the first value logged by this program.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(makeLabel("Ada"));
var prefix = "Hi";
function makeLabel(name) {
  return prefix + ", " + name;
}

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix the early function expression

    Reorder the code so it uses the arrow function after initialization.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    console.log(format("Ada"));
    const format = (name) => "Hi, " + name;

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeExplain typeof

      Which one throws: typeof notDeclared or typeof later before let later = 1?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeReorder to avoid TDZ

        Move only what you need so the program prints the total instead of throwing.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        console.log(total(100));
        const taxRate = 0.08;
        function total(price) {
          return price + price * taxRate;
        }

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeFind the default-parameter TDZ

          Fix the default parameters so calling total() prints 2.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function total(a = b, b = 1) {
            return a + b;
          }
          console.log(total());

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Every choice explains the outcome. For error questions, focus on which kind of error and why.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is hoisting, in the mental model from this lesson?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the function declaration snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(say());
              function say() {
                return "hello";
              }

              Choose an answer to see the explanation.

            3. Question 3 of 7What does the early var snippet print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(score);
              var score = 10;

              Choose an answer to see the explanation.

            4. Question 4 of 7What happens here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(score);
              let score = 10;

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the delayed let read print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function readLater() {
                return secret;
              }
              let secret = "ok";
              console.log(readLater());

              Choose an answer to see the explanation.

            6. Question 6 of 7What is the best description of typeof later before let later = 1?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which early call throws TypeError rather than ReferenceError?

              Choose an answer to see the explanation.

            Key takeaways

            • Hoisting is the creation phase preparing declared names, not text moving.
            • Function declarations are callable before their declaration line runs.
            • var is initialized to undefined; its assignment still runs later.
            • let, const, and class are in the TDZ until initialized.
            • undefined is a value; “not defined” means no binding was found.

            Remember the one-liner.
            Hoisting is JavaScript taking attendance during creation: functions ready, var undefined, TDZ bindings present but unsafe to read.

            Up next: Memory: primitives vs objects.

            CompleteFrontend Clear concepts. Working examples.