Ever seen your JavaScript log undefined for a variable that you clearly declared, or a ReferenceError pop up before a line that seems perfectly fine? Threads across dev communities often boil down to the same core concept: hoisting.
What is hoisting in JavaScript? I keep running into behavior I do not expect: sometimes variables are undefined instead of throwing, function declarations seem callable before I define them, and let or const variables produce temporal dead zone errors.
How does hoisting actually work for var vs let/const, function declarations vs function expressions, and classes? Also, how should I structure my code to avoid surprises in modules and real projects?
Hoisting is JavaScript’s compile-time behavior where declarations are processed before your code executes. In practice, identifiers are known to the engine at the start of their scope, but only some get initialized right away. Understanding which parts are hoisted and how they are initialized prevents subtle bugs. Here’s a breakdown:
vardeclarations are hoisted and initialized to undefined. Accessing them before their assignment givesundefined, not aReferenceError.letandconstare hoisted but not initialized. Accessing them before their declaration throws aReferenceErrordue to the temporal dead zone.- Function declarations are hoisted with their full body. You can call them before the line they appear on.
- Function expressions and arrow functions are variables. They follow
varorlet/construles depending on how you declare them. - Class declarations are hoisted but remain in the temporal dead zone until evaluated.
var gets hoisted and initialized to undefined.
console.log(a); // undefined
var a = 42;
console.log(a); // 42Code language: JavaScript (javascript)
let and const are hoisted but not initialized.
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 1;
console.log(c); // ReferenceError
const c = 2;Code language: JavaScript (javascript)
Function declarations are hoisted with their body.
greet(); // "hi"
function greet() {
console.log("hi");
}Code language: JavaScript (javascript)
Function expressions follow variable rules.
say(); // TypeError: say is not a function
var say = function () { console.log("hello"); };
// With let:
// say(); // ReferenceError
// let say = () => console.log("hello");Code language: JavaScript (javascript)
Classes are in the temporal dead zone.
new Person(); // ReferenceError
class Person {
constructor() { this.name = "Ada"; }
}Code language: JavaScript (javascript)
- ESM imports are hoisted and evaluated before module code runs. Imports must be at the top level and cannot be conditional.
- Because modules run in strict mode, relying on
varhoisting is even more error-prone. Useletandconstinstead.
- Prefer
constby default,letwhen reassignment is needed, and avoid usingvar. - Declare functions before using them, or use function declarations when appropriate.
- Keep related declarations near their usage to make initialization order obvious.
- Enable linter rules like
no-use-before-define.
- If you see undefined where you expected a value, check for
varbeing read before assignment. - If you see a
ReferenceError, verify you are not touchinglet,const, orclassbefore their declaration. - In modules, ensure imports are top level and not conditionally executed.
Suppose you build image URLs dynamically for delivery. Hoisting rules still apply to your helpers and config.
// Bad: use before initialization causes ReferenceError with let/const
const imgUrl = buildUrl("sample"); // ReferenceError if buildUrl is a const below
const buildUrl = (id) => `https://res.cloudinary.com/demo/image/upload/w_400,c_fill/${id}.jpg`;
// Good: either use a function declaration or call after definition
// Option A: function declaration
function buildUrl(id) {
return `https://res.cloudinary.com/demo/image/upload/w_400,c_fill/${id}.jpg`;
}
const imgUrlA = buildUrl("sample");
// Option B: function expression, but call later
const buildUrlB = (id) => `https://res.cloudinary.com/demo/image/upload/w_400,c_fill/${id}.jpg`;
const imgUrlB = buildUrlB("sample");Code language: JavaScript (javascript)
When rendering, make sure your DOM usage is correct and accessible. If you are optimizing how images are embedded, this primer on the HTML image tag is a handy refresher. For bigger-picture delivery strategy and caching behavior, see Understanding image hosting for websites.
- Reading a
varbefore its assignment returnsundefined, not aReferenceError. - Reading
let,const, orclassbefore declaration throws due to the temporal dead zone. - Function declarations are callable before their appearance; function expressions are not.
- Imports are hoisted and must be top level in ES modules.
- Hoisting makes declarations visible at the start of their scope, but only
vargets initialized toundefined. - Use
constandlet, declare functions before use, and avoid relying on hoisting for control flow. - In real apps, especially when building URLs or helpers for media delivery, ensure functions and config are defined before you use them.
Ready to optimize how you manage and deliver media in your JavaScript apps? Sign up for Cloudinary and streamline your asset workflow with powerful transformations and fast delivery.