Originally published on dev.to. Comments and reactions live there.
Updated in 2026. The first version of this post, from 2021, said that
letandconstaren't hoisted. That's a very common misconception, and I believed it too. This rewrite fixes it and turns the post into exercises you can practice with.
In the variables, scope and hoisting post we covered the concepts. Now let's practice the way interviewers actually test them: "What does this code print?"
How to use this post: read each snippet, write down your answer, then open the solution. Run them in your browser console if you want to double-check. Every answer here was tested in Node.js.
Quick refresher
| Declaration | Hoisted? | Before its line |
|---|---|---|
var |
Yes | undefined |
let / const
|
Yes, but uninitialized |
ReferenceError (temporal dead zone) |
function declaration |
Yes, completely | Callable |
| Function expression / arrow | Only the variable | Depends on var, let or const
|
class |
Yes, but uninitialized | ReferenceError |
And remember: the code doesn't move. Before running a scope, the engine registers its declarations. What differs is whether they're initialized.
Exercise 1: the warm-up
console.log(greeting);
var greeting = "Hi";
console.log(greeting);
Solution
undefined, then "Hi".
greeting is registered before the code runs and initialized as undefined. The assignment = "Hi" happens on line 2, so the first log sees undefined.
Exercise 2: let and const
console.log(first);
console.log(second);
let first = "I'm a let";
const second = "I'm a const";
The second line never runs, because the first error stops execution. (In my original post I listed two errors. Only the first one ever appears.) And the key point: Solution
Only one error: ReferenceError: Cannot access 'first' before initialization.
first and second are hoisted. If they weren't, the error would be "first is not defined". "Cannot access before initialization" means the engine knows the variable exists, but it's still in the temporal dead zone.
Exercise 3: three kinds of functions
console.log(sum(2, 3));
function sum(a, b) {
return a + b;
}
console.log(sum2(2, 3));
const sum2 = function (a, b) {
return a + b;
};
Function declarations are hoisted with their body, so you can call them early. Solution
5, then ReferenceError: Cannot access 'sum2' before initialization.
sum2 is just a const variable that happens to hold a function, so it follows const rules. The same applies to arrow functions.
Exercise 4: same function, but with var
sayBye();
var sayBye = function () {
console.log("Bye");
};
Not a Solution
TypeError: sayBye is not a function.
ReferenceError this time. With var, sayBye exists and equals undefined, and calling undefined() is a TypeError. Interviewers love this difference.
Exercise 5: the leaking block
if (true) {
var leak = "var";
let stay = "let";
}
console.log(leak);
console.log(stay);
Solution
"var", then ReferenceError: stay is not defined.
var ignores block scope; it belongs to the enclosing function (or the global scope). let only exists inside the { }.
Exercise 6: shadowing with var
var x = 1;
function test() {
console.log(x);
var x = 2;
}
test();
Not Solution
undefined.
1! Inside test, there's a local var x, which is registered at the start of the function and initialized as undefined. It shadows the global x, so the log sees the local one before its assignment.
Exercise 7: shadowing with let
let x = 1;
{
console.log(x);
let x = 2;
}
You might expect This is the best proof that Solution
ReferenceError: Cannot access 'x' before initialization.
1 from the outer scope. But the inner block has its own let x, hoisted to the top of the block and stuck in the temporal dead zone. The inner x shadows the outer one from the very first line of the block.let is hoisted.
Exercise 8: no declaration inside
var a = 1;
function outer() {
console.log(a);
a = 2;
}
outer();
console.log(a);
There's no Solution
1, then 2.
var, let or const inside outer, so a refers to the outer variable through the scope chain. The function reads it and then reassigns it.
Exercise 9: same name, two declarations
console.log(typeof foo);
var foo = 1;
function foo() {}
console.log(typeof foo);
Both declarations are registered, and the function declaration wins during hoisting. Then the line Solution
"function", then "number".
foo = 1 runs and replaces it with a number. (Please never write this in real code.)
Exercise 10: typeof and the TDZ
console.log(typeof notDeclared);
console.log(typeof inTDZ);
let inTDZ = "hello";
Solution
"undefined", then ReferenceError: Cannot access 'inTDZ' before initialization.
typeof is usually "safe": on a variable that doesn't exist at all, it returns "undefined" instead of throwing. But a variable in the temporal dead zone is different. It exists, it's just not initialized, and typeof can't save you.
Bonus round
Two quick ones that show up in more advanced interviews:
new User();
class User {}
function greet(a = b, b = "hi") {
return a;
}
greet();
Solutions
Both throw a ReferenceError.
let: they're in the temporal dead zone until their declaration.a tries to read b, b hasn't been initialized yet.
How did you do?
- 10 to 12: you're ready for this part of the interview.
- 6 to 9: review the temporal dead zone and shadowing (exercises 6, 7 and 10).
- Under 6: go back to the concepts post and try again in a couple of days.
Which one got you? Mine was exercise 6 for a long time. Tell me yours in the comments 👇