completefrontend
Open lab My progress
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
    Watch names get set upStep through the creation phase, when JavaScript sets up every name before line 1 runs.
  • 02
    Predict early readsSay what happens when code reads a function, a var, a let, a const, a class, or a missing name too early.
  • 03
    Fix timing bugsReorder code so that every name has its value before it is read or called.

Names are set up before line 1 runs

STEP THROUGH

In the Execution contexts lesson, you saw that JavaScript runs your code in two phases:

  • The creation phase: before any line runs, JavaScript reads through the code and sets up every name it declares.
  • The execution phase: JavaScript runs the lines one by one, from top to bottom.

A declaration is the code that creates a name, like var total, let theme, or function greet(). Hoisting is what you see because of the creation phase: some names can be used on lines above their declaration.

Each kind of declaration starts in a different state:

  • A function declaration is ready to call.
  • A var name holds undefined, the value that means "nothing here yet".
  • A let, const, or class name exists, but you can't read it yet. It gets its first value only when its own line runs.

The time before that line runs is called the temporal dead zone, or TDZ. "Temporal" means "about time". Reading the name during that time throws an error.

A var total starts as undefinedPop out in the code editor (opens in a new tab)JavaScript
console.log(total);
var total = 5;
console.log(total);

Line 1 reads total before line 2 gives it a value, so it prints undefined. Line 2 stores 5. Line 3 prints 5. The name was set up early. The value came only when line 2 ran.

Be careful: the first undefined is not a real total. It is a bug that stays quiet. With let or const instead of var, line 1 would throw an error. The error points you straight at the line that reads too early.

The short version

Hoisting does not move your code. It means JavaScript sets up every declared name before line 1 runs. What an early read gives you depends on the kind of name:

  • A function declaration gives a ready function.
  • A var gives the value undefined.
  • A let, const, or class throws an error, because the name is in its TDZ.
Real-life analogyTaking attendance before the lesson

A teacher takes attendance before the lesson starts. Some students are ready, with notebooks open. Some are in the room but have brought nothing. Some are in the room but asleep. And some names are not on the class list at all.

In real life: The teacher reads the class list before the lesson starts
In JavaScript: JavaScript sets up every declared name before line 1
In real life: A student is ready, with a notebook open
In JavaScript: A function declaration can be called right away
In real life: A student is there, but has brought nothing
In JavaScript: A var name exists and holds undefined
In real life: A student is there, but asleep
In JavaScript: A let, const, or class name is in its TDZ
In real life: A name that is not on the class list
In JavaScript: A name with no declaration is "not defined"
Before line 1 runs, three boxes already exist. The greet box holds a ready function. The score box holds undefined. The status box is empty and can't be read yet.function greetgreet()greetready to callvar scoreundefinedscoreholds undefinedlet statusstatuscan't read yet
Before line 1 of the replay below runs, JavaScript has already made a box for every name. The function greet is ready to call. The var name score holds undefined. The let name status has a box, but you can't read it until line 4 runs.

Now try the replay. Its code has a function, a var, and a let. You choose what line 2 reads: the var, the let, or a name that was never declared. The results are what real JavaScript does. The tests for this lesson run each version to check them.

Names set up before line 1
Step 0 of 8Ready
Your turn: follow the blue line

Before line 1, JavaScript sets up every declared name. Change line 2, then guess: does it print a value or throw?

Running in
  1. script
Next: line 5
console.log(greet());console.log(score);var score = 7;let status = "ready";console.log(status);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose what line 2 reads too early

Changing line 2 starts the replay again. Guess first: does it print a value, throw because of the TDZ, or throw because 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 declarations are ready early

A function declaration is a line that starts with the word function and gives the function a name, like function double(n). During the creation phase, JavaScript makes the whole function and puts it in that name. So you can call it on a line above the declaration:

Call a function declaration before its linePop out in the code editor (opens in a new tab)JavaScript
console.log(double(4));
function double(n) {
  return n * 2;
}

Line 1 calls double(4). That works, because double was set up before line 1 ran. The function body, return n * 2, does not run during the creation phase. It runs only when line 1 calls the function. So line 1 prints 8.

Functions inside a block, in strict code

A block is code inside curly braces, like the body of an if. Strict mode is the modern set of rules you turn on with "use strict". In strict mode, a function declared inside a block belongs to that block only.

Older non-strict code has a different, confusing rule for this. It exists only so that old websites keep working, and it can differ between browsers. Don't rely on it. This course writes strict, modern code.

In strict code, a block's function stays in the blockPop out in the code editor (opens in a new tab)JavaScript
"use strict";
if (true) {
  function greet() {
    return "inside";
  }
  console.log(greet());
}
console.log(typeof greet);

Line 6 is inside the if block. It calls greet() and prints inside. Line 8 is outside the block. There is no greet out there, so typeof greet prints undefined.

typeof gives the kind of a value as text, like "number" or "function". For a name that doesn't exist, it gives "undefined" instead of throwing an error.

Non-strict code uses an old rule for block functionsPop out in the code editor (opens in a new tab)JavaScript
if (true) {
  function greet() {
    return "inside";
  }
}
console.log(typeof greet);

This is the same code without "use strict". When Node.js runs it as a plain script, line 6 prints function. The old rule copied greet outside the block. Don't depend on this. Keep a block's functions inside the block. If you need a helper outside the block, declare it outside on purpose.

Function declarations also help you organize code. Many teams put the main steps at the top of a file and the helper functions below. That works only when the helpers are function declarations, not functions saved in variables.

var names start as undefined

CONTRAST

var is the oldest way to declare a variable. It is older than let and const. During the creation phase, a var name is set up and given the value undefined. The assignment, like = 7, stays on its own line. It runs only when that line is reached.

The assignment stays on its own linePop out in the code editor (opens in a new tab)JavaScript
console.log(score); // undefined
var score = 7;
console.log(score); // 7

Line 1 prints undefined, because line 2 hasn't run yet. Line 2 stores 7. Line 3 prints 7.

Left: on line 1 the score box holds undefined, so line 1 prints undefined. An arrow labelled line 2, score = 7, leads to the right: the score box now holds 7, so line 3 prints 7.Line 1 reads scoreundefinedscoreprints undefinedscore = 7line 2Line 3 reads score7scoreprints 7
The box called score exists from the start, but it holds undefined. Line 2 puts 7 in the same box. So line 1 prints undefined, and line 3 prints 7.

This is one reason modern code usually uses let and const instead (see Variables: let, const & var). With var, an early read gives a value instead of an error. If you expected 7, undefined is still a bug. It is just harder to notice.

Real-life analogyA reserved parking space

A var name is like a reserved parking space. The sign is there from the start, but the space stays empty until the car arrives. If you look early, you find the space, and it's empty.

In real life: The sign says this space is for Ada
In JavaScript: The var name exists from the creation phase
In real life: Ada's car hasn't arrived yet
In JavaScript: The value is undefined until the assignment runs
In real life: The car arrives later
In JavaScript: The assignment line stores the real value

let, const & class: the temporal dead zone

STEP THROUGH

let, const, and class names are also set up during the creation phase. But they get no value at all, not even undefined. A name with no value yet is called uninitialized. It stays that way until its own line runs.

The time from the start of the block until that line runs is the temporal dead zone (TDZ). Any read during that time throws a ReferenceError. That is the error JavaScript throws when it can't use a name. "Temporal" means the TDZ is about when the read happens, not where it is written.

Reading too early throwsPop 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 {}

Line 1 reads theme before line 2 gives it a value, so it throws a ReferenceError, and the program stops there.

Left: on line 1 the theme box is empty, so reading it throws a ReferenceError. An arrow labelled line 2, let theme = dark, leads to the right: the theme box now holds dark and is ready to read.Line 1 reads themethemeReferenceErrorlet theme = "dark"line 2After line 2"dark"themeready to read
The box called theme exists from the start, but it stays empty and locked until line 2 runs. Reading it on line 1 throws a ReferenceError. After line 2, it holds "dark".

Lines 4 and 5 show the same rule for a class. Run on their own, they throw too: typeof Widget on line 4 reads the class name before line 5 has run. You'll learn classes in Class basics later. For now, remember that a class name follows the same rule as let.

Imports are set up before the module's code runsJavaScript
console.log(label);
import { label } from "./value.mjs";

This example only works in a module: a JavaScript file that can use import and export. Line 1 reads label, and line 2 imports it from another file. It still works, and prints ready. Before any line of a module runs, JavaScript loads every import and gives those names their values.

The editor on this page can't run code that is split across two files. So the lesson test runs this exact example with a second file, value.mjs, which exports label.

The TDZ is about time, not about where code is written. In the replay below, a function that reads secret is written above let secret. But it is called after line 4 runs, so the read is safe.

Written above the let, called after it
Step 0 of 5Ready
Your turn: follow the blue line

The TDZ is about when a read runs, not where it is written. This function is written early, but called late.

Running in
  1. script
Next: line 1
  return secret;}let secret = "initialized";console.log(readLater());
CallStoreChangeResultBlue line: runs next. 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.
typeof is not always safe

typeof notDeclared gives "undefined", because there is no name notDeclared at all. But typeof later, on a line before let later, throws an error. The name later does exist; it just has no value yet. So "never declared" and "declared, but no value yet" behave differently.

typeof is safe only for a missing namePop out in the code editor (opens in a new tab)JavaScript
console.log(typeof notOnTheRoster);

Nothing in this code declares notOnTheRoster. JavaScript looks for the name, finds nothing, and typeof gives "undefined". This safe answer is only for names that don't exist. It doesn't help with a name that is still in its TDZ:

typeof still throws in the TDZPop out in the code editor (opens in a new tab)JavaScript
console.log(typeof later);
let later = 1;

Here, line 2 declares later, so the name exists. But on line 1 it has no value yet. typeof still has to read it, so it throws a ReferenceError.

An inner let hides the outer one, even before its linePop out in the code editor (opens in a new tab)JavaScript
let theme = "light";
{
  console.log(theme);
  let theme = "dark";
}

Line 1 makes an outer theme with the value "light". Lines 2 to 5 are a block with its own let theme on line 4. When a block declares a name that already exists outside, the inner name shadows the outer one: inside the block, only the inner name can be seen.

This starts at the top of the block, not at line 4. So line 3 does not read "light". It reads the inner theme, which has no value yet, and throws a ReferenceError.

This mistake is easy to make in long functions with blocks inside blocks. To fix it, give the inner variable a different name. Or give it its value before the first read. Don't expect the outer value to be used.

Default parameters can have a TDZ too. A default parameter is the value a parameter uses when the caller doesn't pass one, like b = 1. Parameters get their values from left to right. So in function f(a = b, b = 1) {}, a tries to read b before b has a value, and the call throws. Swap them to function f(b = 1, a = b) {} and it works.

Function expressions and arrows follow their variable

INTERACTIVE

A function expression is a function written as a value and saved in a variable, like var double = function (n) { … }. An arrow function is a shorter way to write one, like const double = (n) => n * 2.

Either way, the function is made only when its line runs. Until then, the variable follows its own rule: a var holds undefined, and a const is in its TDZ. Pick each version in the replay and watch what line 1 does.

Call double before its line, three ways
Step 0 of 4Ready
Your turn: follow the blue line

Pick how double is made, then guess what line 1 does: print a value, throw a TypeError, or throw a ReferenceError.

Running in
  1. script
Next: line 2
console.log(double(4));  return n * 2;}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose how double is made

Only the function declaration is ready before line 1. The other two fail, each with a different error.

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.
Line 1 calls double(4) early, in three versions. With function double, the box holds a ready function and it prints 8. With var double, the box holds undefined and the call throws a TypeError. With const double, the box is empty and the call throws a ReferenceError.function doubledouble()doubleprints 8var doubleundefineddoubleTypeErrorconst doubledoubleReferenceError
In all three versions, line 1 calls double(4) before line 2 runs. Only the function declaration is ready. The var box holds undefined, and calling undefined is a TypeError. The const box can't be read yet, so the call fails with a ReferenceError.

Here is what each version does on line 1:

  • Function declaration: double is ready, so line 1 prints 8.
  • var with a function expression: double holds undefined. Calling undefined throws a TypeError. That is the error for using a value in a way that doesn't work.
  • const with an arrow: double is in its TDZ. Just reading it for the call throws a ReferenceError.

The earlier Function expressions & callbacks lesson showed how to write these. This lesson explains why they behave differently when you call them early.

undefined vs not defined

These two sound alike, but they mean different things:

  • undefined is a real JavaScript value. It means "this name exists, but holds nothing useful yet".
  • "Not defined" is part of an error message, like ReferenceError: x is not defined. It means JavaScript looked for the name and found no declaration for it anywhere.

The table below shows what happens when code reads a name too early. Read each row as: "when the name is this kind, an early read gives this, because …"

What an early read gives, for each kind of name
What the name isWhat an early read givesWhy
A function declarationA function you can call.The whole function was put in the name before line 1 ran.
var score, read before its assignmentundefinedThe name was set up with undefined. The assignment runs later, on its own line.
A var that was never given a valueundefinedThe name exists, but nothing was ever stored in it.
let, const, or class, read before its lineReferenceErrorThe name exists, but it has no value until its line runs. It is in its TDZ.
A name that was never declaredReferenceError: name is not definedJavaScript looked for the name and found no declaration for it.
var fn = function …, called earlyTypeErrorThe name holds undefined, and you can't call undefined.

Now sort the cases yourself. Put each line of code in the group for what it really does. This checks that you can tell a real undefined apart from a ReferenceError and a TypeError.

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

Put each line of code in the group that matches what it really does when it runs.

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

Where you’ll use this

Hoisting matters whenever you arrange code so that people can read it easily. For example, you might:

  • put helper functions below the main code,
  • split setup code across a file,
  • fix an error whose line number points above a declaration.
Main code first, helper belowPop out in the code editor (opens in a new tab)JavaScript
console.log(renderGreeting("Ada"));

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

Line 1 calls renderGreeting("Ada") and prints Hello, Ada. The helper is declared on line 3, below the call. That works because it is a function declaration, so it was ready before line 1 ran.

Still, keep your code simple:

  • Declare each variable near the top of the block where you use it.
  • Give a const its value on the same line.
  • Never depend on reading a var early.

Knowing about hoisting helps you find bugs. It is not a reason to write tricky code.

var belongs to the function, let belongs to the blockPop out in the code editor (opens in a new tab)JavaScript
if (true) {
  var banner = "Sale";
  let secret = "coupon";
}
console.log(banner); // Sale
console.log(typeof secret); // undefined

Lines 2 and 3 run inside the if block. A var name belongs to the whole function or script around it, so banner still exists after the block. Line 5 prints Sale.

A let name belongs only to its block, so secret is gone after the closing brace on line 4. Line 6 uses typeof, which is safe for a missing name, so it prints undefined. Because of this difference, take care when you change old var code to let. Check each change; don't swap them all without looking.

The next example uses setTimeout. It runs a function later, after the current code has finished. Each loop below asks for three functions to run later, and each function prints index.

Loop callbacks see var and let differentlyJavaScript
for (var index = 0; index < 3; index++) {
  setTimeout(() => console.log("var", index), 0);
}
for (let index = 0; index < 3; index++) {
  setTimeout(() => console.log("let", index), 0);
}

The first loop prints var 3 three times. All three functions share one index, because a var belongs to the whole file, not to the loop. When the functions finally run, the loop has finished and index is 3.

The second loop prints let 0, then let 1, then let 2. With let, each turn of the loop gets its own index.

This bug shows up on real pages with click handlers and timers. Because the code uses setTimeout, the editor on this page doesn't run it. The lesson test runs it in Node.js and checks all six lines.

Put a value before the helper that reads itPop out in the code editor (opens in a new tab)JavaScript
const order = "A12";
console.log(order);

// ESLint: no-use-before-define catches this when order is below its use.
function printOrder() {
  return order;
}

Line 1 gives order its value first. Line 2 prints A12. The helper printOrder on lines 5 to 7 reads order, and it is written after order has its value.

A linter is a tool that checks your code for mistakes before it runs. The ESLint linter has a rule, no-use-before-define, that warns when code uses a name above its declaration. When you see that warning, move the setup up so the order is clear.

Common misconceptions

“Hoisting moves my code to the top.”

No. Your code stays where you wrote it. The creation phase only sets up names. Then the lines run in the order you wrote them.

“Only functions are hoisted.”

No. Function declarations are the easiest to notice. But var, let, const, and class names are all set up in the creation phase too.

“The TDZ means the read is written above the declaration.”

No. The TDZ is about when the read runs. A function written above a let is fine if you call it after the let line runs.

“typeof never throws.”

It is safe for names that were never declared. It still throws for a name in its TDZ.

“ReferenceError and TypeError mean the same thing.”

No. A ReferenceError means JavaScript couldn't use the name: it doesn't exist, or it is in its TDZ. A TypeError means JavaScript found a value, but you can't do that with it, like calling undefined.

When an early read surprises you, ask three questions in order:

  1. What kind of declaration made the name?
  2. Has its line run yet?
  3. Can you do this with the value you found? For example, is it a function you can call?

The answers tell apart a TDZ ReferenceError, a var that holds undefined, and a TypeError from calling something that isn't a function.

Steps to fix an early-read bug
  • Find the line that reads the name. The error message gives its line number. Don't only look at the declaration below it.
  • Check what made the name: a function declaration, var, let, const, class, an import, or nothing at all.
  • Move the line that gives the value above the read. Or move the read to a place where the name already has its value.

Practice: predict and fix timing bugs

5 EXERCISES

Guess what each program does before you run it. Use the hints if you get stuck.

Exercise 1 · Warm-upPredict a var read

What does line 1 print? Write the exact text.

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 arrow function

    Line 1 calls an arrow function before it exists. Reorder the lines so the call comes after the function is made. What does the fixed code print?

    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 on a line before let later = 1?

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

        Exercise 4 · PracticeReorder to avoid the 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 that 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

            For each code question, first ask what kind of declaration made the name. Each choice explains why it is right or wrong.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What is hoisting?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this early function call 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 this early var read 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 7The function is written above the let. What does this code 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 happens with typeof later on a line before let later = 1?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which early call throws a TypeError, not a ReferenceError?

              Choose an answer to see the explanation.

            Key takeaways

            • Hoisting means JavaScript sets up every declared name before line 1 runs. Your code does not move.
            • You can call a function declaration on a line above it.
            • A var name starts as undefined. Its assignment still runs on its own line.
            • let, const, and class names are in their TDZ until their line runs. Reading them earlier throws.
            • undefined is a value. "Not defined" means JavaScript found no declaration for the name.

            Remember the one-liner.
            Before line 1, every name is set up: functions are ready, var holds undefined, and let, const, and class wait for their own line.

            Up next: Memory: primitives vs objects.

            CompleteFrontend Clear concepts. Working examples.