Hoisting and the temporal dead zone: why some names exist before their line
JavaScript sets up every name in a scope before running it. Learn what var, function, let and const look like before their line, and what the TDZ is.
Comes up in: JavaScript, Recruiter screen
Learn#
Vikram moves a helper function to the bottom of his file to tidy it, and everything still works. Then he moves a const config the same way, and the app crashes with “Cannot access ‘config’ before initialization”. Same move, different result.
JavaScript runs each scope in two steps. First it creates every name declared in that scope. Then it runs the lines in order. Names existing before their line is called hoisting. What differs is the starting value.
A function declaration starts as the full function, so you can call it early.
A var starts as undefined.
A let, const or class starts uninitialised. From the top of the scope until its line runs — its initialisation — it is in the temporal dead zone (TDZ), and any access throws.
console.log(typeof greet, greet());
function greet() { return "hello"; }
console.log(count);
var count = 5;
try { console.log(total); } catch (e) { console.log(e.name + ": " + e.message); }
let total = 10;Output (verified)
function hello undefined ReferenceError: Cannot access 'total' before initialization
The error message is V8’s (Chrome, Node.js); Firefox words it differently.
A function expression like const f = () => {} follows the rules of const, not of declarations.
names in scope
- greet = function (ready)
- count = undefined
- total = (uninitialised)
Console
empty
Before the first line runs, JavaScript scans the scope and creates every name.
Read the steps as text
- Before the first line runs, JavaScript scans the scope and creates every name.
- Line 1: greet is already a full function, so calling it works.
- count exists but holds undefined until its var line runs.
- total exists but is in the temporal dead zone. Reading it throws.
- Once the let line runs, total is initialised and safe to use.
Predict#
Predict first
sayHi(); var sayHi = function () { console.log("hi"); }; — what happens?
Only the var name is hoisted, as undefined; the function value is assigned when that line runs. Calling undefined is a TypeError. People who answer “prints hi” are thinking of a function declaration, which is hoisted with its body.
Interview angle#
Say it like this — about 30 seconds
“Before a scope runs, JavaScript creates all its declared names. Function declarations are created with their body, so you can call them early.
varis created asundefined.let,constandclassare hoisted too, but stay uninitialised until their line runs — the temporal dead zone — and any access there throws a ReferenceError, eventypeof. Function expressions follow their variable’s rule.”
Follow-up Are let and const hoisted?
Follow-up What does typeof return for a variable in the TDZ?
typeof only returns “undefined” for names that do not exist at all.Follow-up Why is the TDZ useful?
const meaningful: a const is never seen holding a temporary undefined.Practice#
faded
makeLogger crashes on its first line. Find which name is in the temporal dead zone and fix the order.
Done when
makeLogger()does not throw and returns a function.log("saved")returns"[app] saved".
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What is hoisting?
Every name in a scope is created before the scope's code runs.
What value does a var have before its line runs?
undefined.
What is the temporal dead zone?
The time before a let, const or class line runs, when any access throws.
What does this print?try { typeof later; } catch (e) { console.log(e.name); }
let later = 1;
try { typeof later; } catch (e) { console.log(e.name); }
let later = 1;ReferenceError
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
All names are created first. Function declarations start ready, var starts as undefined, and let/const/class cannot be touched until their line.
Check yourself: Which can you safely call before its line: a function declaration or an arrow function in a const?
The function declaration.
How it works underneathSenior rounds
When a function is called, the spec runs FunctionDeclarationInstantiation: it creates bindings for parameters, vars and functions (initialising functions with their value), and creates let/const bindings without initialising them Source: ECMA-262: FunctionDeclarationInstantiation. Reading an uninitialised binding throws a ReferenceError.
Check yourself: What does the spec call the step that creates the names?
Declaration instantiation (for example, FunctionDeclarationInstantiation).
Edge cases and trapsWhere people slip
let x = x + 1 throws: the right side runs before x is initialised. A default parameter cannot read a later parameter. In a block, an inner let x shadows the outer x from the block’s first line, so code above the inner declaration throws instead of reading the outer value Source: MDN: let — temporal dead zone.
Check yourself: Why does `let x = x + 1` throw?
The right side reads x while x is still in its TDZ.
In productionStaff-level answers
Declare before use, even for functions; it reads top to bottom. Turn on ESLint no-use-before-define and no-var. Circular imports can also expose a TDZ: a module may run before the const it imports is initialised, which shows up as “Cannot access … before initialization”.
Check yourself: Which ESLint rule reports use before definition?
no-use-before-define.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is hoisting? Are
letandconsthoisted? - What is the temporal dead zone?