Hoisting is the behavior where JavaScript processes variable and function declarations before running the rest of a scope's code. It looks like the declaration moved to the top, which is where the name comes from, but nothing actually moves. The engine scans a scope before executing it and registers the names it finds, and what happens if you reference one of those names early depends entirely on how it was declared.
What changed in 2026
- Linting defaults catch most hoisting bugs before they ship. ESLint's recommended configuration and most 2026 starter templates, including Vite and Next.js defaults, enable rules against
var and against using a name before its definition, so many hoisting surprises never reach production.
- Editors flag temporal dead zone violations while typing. TypeScript's control-flow analysis and strict mode catch most early-access bugs at compile time, turning what used to be a runtime surprise into a red underline in the editor.
- A new generation of developers meets the temporal dead zone before the var quirk. AI assistants almost always generate
let and const, rarely var, so newer developers often encounter the temporal dead zone first and the classic var-returns-undefined behavior later, usually while reading legacy code.
What actually gets hoisted
console.log(x); // undefined, not an error
var x = 5;
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
Both x and y are registered during the setup pass. var gets an initial value of undefined immediately, so reading it early just returns that. let and y exist too, but sit in a state where reading them throws instead.
var, let, const, and function declarations compared
| Declaration |
Hoisted? |
Value before its line |
Error if accessed early? |
var |
Yes |
undefined |
No |
let / const |
Yes, name only |
Not accessible (temporal dead zone) |
Yes, reference error |
function foo() {} |
Yes, fully, with body |
Callable |
No |
const foo = () => {} |
Name only, not the value |
Not accessible |
Yes, reference error |
The temporal dead zone
The temporal dead zone is the span between the start of a scope and the line where a let or const variable is actually declared. During that span, the variable exists, which is why it can shadow an outer variable of the same name, but it cannot be read or written.
let value = "outer";
function demo() {
console.log(value); // ReferenceError, not "outer"
let value = "inner"; // temporal dead zone ends here
}
demo();
Even though an outer value exists, the inner let value shadows it for the entire function, including before its own declaration line, which is exactly why this throws instead of logging "outer."
Function declarations vs function expressions
sayHi(); // works: "Hi!" — full function is hoisted
function sayHi() { console.log("Hi!"); }
sayBye(); // TypeError: sayBye is not a function
var sayBye = function () { console.log("Bye!"); };
Only the declaration form is hoisted along with its body. A function expression assigned to a variable follows that variable's own hoisting rules: the variable name is hoisted, but the function assigned to it is not available until that line actually runs.
Why this matters day to day
Recognizing the temporal dead zone turns a "cannot access before initialization" error from a mystery into a two-second fix, instead of a suspected import or scope problem. Relying on function-declaration hoisting to call a helper before its definition works, but it hurts readability, and most style guides ask you to define things before use anyway. Mixing var and let in the same file makes hoisting behavior inconsistent within one scope; sticking to let and const removes an entire category of surprise. It also affects closures directly: see what a closure is in JavaScript for why a var inside a loop is captured differently than a let.
FAQ
Does hoisting mean my code actually gets reordered?
No. Nothing moves. The engine registers declared names during a setup pass before executing the scope, which looks like reordering from the outside, but the source code is untouched.
Why does let throw an error instead of just returning undefined like var?
It is a deliberate design choice from ES2015, meant to catch a category of bugs early. Reading a variable before its declaration line is almost always a mistake, so let and const fail loudly instead of silently.
Are class declarations hoisted?
The name is hoisted like let, into the temporal dead zone. A class cannot be constructed before its declaration line, even though the name technically exists earlier in the scope.
Is hoisting a JavaScript-only concept?
The term is most associated with JavaScript, but similar declaration-before-execution behavior appears in other languages under different names.
Where to go next