Hoisting and the Temporal Dead Zone in JavaScript
Continuing the interview prep series. After the event loop and closures, the next topic is hoisting. It comes up less often, but “variables get hoisted” is too weak an answer. Let’s see how it works under the hood.
What is hoisting
JavaScript executes code in two phases. First it goes through all the code in the file and registers all declarations: variables, functions, classes. Then it executes code line by line.
Hoisting is a consequence of this two-phase process. It’s the behavior where declarations are stored in memory before execution reaches them.
Seems straightforward, but there’s a catch: not all declarations are stored in memory the same way.
function declaration
A function declaration is stored in memory entirely, along with its body. You can call it before its declaration in the code.
greet("Bob"); // 'Hello, Bob' — works
function greet(name) {
console.log(`Hello, ${name}`);
}
During the creation phase, the engine found function greet and stored it in memory right away. By the time greet('Bob') is called, the function was already available, even though it was declared lower in the code.
The same applies to async functions async function and generators function*: if a declaration starts with the function keyword, it’s hoisted entirely.
var
With var the behavior is slightly different: only the variable declaration goes into memory, with the value undefined. The assignment happens later, when execution reaches that line.
console.log(name); // undefined — not an error
var name = "Bob";
console.log(name); // 'Bob'
During the creation phase, the engine saw var name and reserved a spot in memory with the value undefined. The assignment 'Bob' happens when execution reaches that line.
let and const: TDZ
let and const are also registered during the creation phase, but not initialized — they don’t even get the value undefined. They exist in memory, but can’t be accessed — any attempt before the declaration line will throw a ReferenceError.
That window is called the Temporal Dead Zone, TDZ.
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 25;
console.log(typeof age); // ReferenceError — typeof doesn't help inside TDZ
let age = 25;
Unlike var, which silently returns undefined, accessing let/const before the declaration throws a ReferenceError — stricter and more predictable behavior, less chance of missing a bug.
function expression and arrow functions
If a function is assigned to a variable, hoisting follows the rules of that variable, not the rules of the function.
greet(); // TypeError: greet is not a function
var greet = function () {
console.log("Hello");
};
During the creation phase, only var greet with the value undefined went into memory. Before the assignment line, greet is just undefined, not a function. Calling undefined() gives a TypeError.
greet(); // ReferenceError: Cannot access 'greet' before initialization
const greet = () => {
console.log("Hello");
};
In this example the arrow function is declared with const, so it falls into the TDZ — trying to call it before the declaration throws a ReferenceError.
class
The engine handles a class declaration like let/const: it registers it during the creation phase but doesn’t initialize it — the class also falls into the TDZ.
const user = new User("Bob"); // ReferenceError
class User {
constructor(name) {
this.name = name;
}
}
Unlike function declaration, you can’t use a class before its declaration.
Classic interview question
What does this code output?
console.log(typeof foo);
console.log(typeof bar);
var foo = 1;
function bar() {}
Without knowing how hoisting works, expectations can vary: an error or undefined for both. Actually:
typeof foo—'undefined':var foowas registered during the creation phase with the valueundefined,typeof undefinedreturns the string'undefined'typeof bar—'function':function barwas stored in memory as a function declaration — along with its body
Now a harder version:
var foo = 1;
function foo() {
return 2;
}
console.log(typeof foo); // 'number'
During the creation phase, function foo is stored in memory first. Then execution reaches var foo = 1 and overwrites the value with a number. The var foo declaration is ignored — the name is already reserved by the function, but the assignment runs.
Why it comes up in interviews
Hoisting is not a standalone language feature — it’s more of a side effect of how the engine reads code.
A few situations where not knowing hoisting causes problems:
- Using
varbefore the declaration. The variable already exists in memory with the valueundefined, even though the assignment hasn’t happened yet. The code doesn’t throw an error, it just silently returnsundefinedinstead of the actual value. - The temporal dead zone with
letandconst. The variables are already registered, but can’t be accessed — accessing them before the declaration line throws aReferenceError. - The difference between
function declarationandfunction expression. The first is hoisted entirely and available before the declaration. The second is not. They look similar, behave differently. - Mixing
function declarationandvarwith the same name. The execution order doesn’t match the order in the code, and the result depends on what exactly went into memory and when.
In an interview, this topic shows whether you understand how the engine executes code — and how not to shoot yourself in the foot with its traps.
← Back to Blog