Suggest an editImprove this articleRefine the answer for “hoisting in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Hoisting** is JavaScript's behavior of moving declarations to the top of their scope before code runs. ```javascript console.log(x); // undefined - var is hoisted, initialized to undefined var x = 5; console.log(y); // ReferenceError - let/const stay uninitialized (TDZ) let y = 5; greet(); // works - function declarations are fully hoisted function greet() { console.log("Hi"); } ``` **Key:** Function declarations hoist with their full body; `var` hoists as `undefined`; `let`/`const` hoist but are inaccessible until their declaration line.Shown above the full answer for quick recall.Answer (EN)Image**Hoisting** is JavaScript's behavior of registering variable and function declarations in memory during compilation, before the first line of code runs. ## Theory ### TL;DR - Think of a roll call before class: the teacher marks everyone present before the lesson starts. Declarations are noted, but actual values come later. - Function declarations are fully hoisted. You can call them before they appear in the file. - `var` declarations are hoisted and initialized to `undefined`. Accessing one before its assignment line returns `undefined`, not an error. - `let` and `const` are hoisted but not initialized. Accessing them before their declaration throws a `ReferenceError`. This gap is called the temporal dead zone (TDZ). - Default choice: `const` for values that won't change, `let` for variables that will, `var` only in legacy codebases. ### Quick example ```javascript // Function declaration - fully hoisted, this works console.log(add(2, 3)); // 5 function add(a, b) { return a + b; } // var - hoisted as undefined console.log(name); // undefined (not an error) var name = "Alice"; // let/const - temporal dead zone console.log(age); // ReferenceError: Cannot access 'age' before initialization let age = 25; ``` The engine already knows about `add`, `name`, and `age` before line 1 runs. But only `add` has its full value at that point. ### Key difference The JavaScript engine makes two passes over your code. In the first pass (compilation), it scans for all declarations and registers them in memory. Function declarations get their full function object right away. `var` variables get `undefined`. `let` and `const` get registered but stay uninitialized, which is why accessing them early throws instead of quietly returning `undefined`. That difference is intentional: the TDZ exists to catch bugs that `var` used to hide. ### When to use - **Function declarations:** When you need to call a helper at the top of a file while defining it below. Common in Express route files and Node.js modules. - **`var`:** Avoid in modern code. The only real case is maintaining code that must run in IE8 or older environments. - **`let`/`const`:** Default for all new code. `const` prevents accidental reassignment; `let` signals the value will change. ### How the engine handles this V8 (Chrome/Node.js) and SpiderMonkey (Firefox) both do two passes. Pass one: scan the scope, allocate memory, set initial values. Pass two: execute line by line. For `var` and function declarations, pass one sets a real initial value. For `let`/`const`, pass one only registers the name. The value comes in pass two, at the exact declaration line. ### Common mistakes **Mistake 1: Expecting `var` to respect block scope** ```javascript function test() { console.log(y); // undefined - not an error if (true) { var y = 10; } console.log(y); // 10 - y is function-scoped, not block-scoped } test(); ``` `var` ignores block boundaries. `y` is hoisted to the function scope, so both `console.log` calls see it. The first returns `undefined`, the second returns `10`. Switching to `let` makes the first line throw a `ReferenceError`, which is usually the behavior you actually want. **Mistake 2: Refactoring a function declaration into an arrow function** ```javascript // Works today processData(rawData); function processData(data) { return data.map(x => x * 2); } // After a refactor - breaks processData(rawData); // TypeError: processData is not a function const processData = (data) => data.map(x => x * 2); ``` Arrow functions and function expressions are not hoisted. If someone converts a function declaration to a `const`, any call above the definition breaks immediately. I've seen this burn teams during refactors where CI passed locally but failed once file order changed in the build. **Mistake 3: The classic `var` loop closure bug** ```javascript // Prints 3, 3, 3 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } // Prints 0, 1, 2 for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); } ``` `var i` is hoisted to the function scope. All three callbacks reference the same `i`, which equals `3` by the time they run. `let i` creates a new binding per iteration. **Mistake 4: Thinking function expressions are hoisted** ```javascript foo(); // TypeError: foo is not a function var foo = function() { return 42; }; ``` `var foo` is hoisted as `undefined`. The function body is not. Calling `foo()` before the assignment is calling `undefined()`. ### Real-world usage - **Express.js:** Route handlers as function declarations can reference middleware defined below them in the same file. - **Node.js modules:** Public functions at the top, private helpers at the bottom. Works because function declarations are fully hoisted within the module. - **Jest/Mocha:** `describe()` blocks can reference test helper functions defined after them. - **React class components:** Lifecycle methods and `render()` can call helper methods defined anywhere in the class body. ### Follow-up questions **Q:** What is the temporal dead zone? **A:** The TDZ is the period between entering a scope and reaching the `let`/`const` declaration line. During this window, the variable is registered but not initialized. Accessing it throws a `ReferenceError`. This differs from `var`, which is initialized to `undefined` the moment the scope starts. **Q:** Why does `console.log(x)` print `undefined` instead of throwing when `var x` is declared below? **A:** Because `var x` is hoisted and immediately initialized to `undefined`. The engine treats it as `var x = undefined; console.log(x); x = 5;`. The assignment stays on the original line, but the declaration with its default value moves up. **Q:** Can you hoist a function expression or arrow function? **A:** No. Only function declarations hoist with their full body. Function expressions assigned to `var` hoist as `undefined`. Assigned to `let`/`const`, they sit in the TDZ. Calling either before the line throws. **Q (Senior):** `let` and `const` are hoisted but not initialized. Why does the spec bother hoisting them at all? **A:** Because hoisting determines scope ownership. If `let x` is inside a block, JavaScript needs to know that `x` belongs to that block, not an outer scope. Without registration in pass one, the engine would walk up the scope chain and potentially find an outer `x`, giving you a value instead of an error. The TDZ guarantees the variable is claimed by its block before becoming accessible. ## Examples ### Function declaration hoisting inside a module ```javascript // Calling before definition - works because of hoisting const logger = createLogger("APP"); logger("Server started"); // [APP] Server started function createLogger(prefix) { return function(message) { logMessage(message); }; // logMessage is hoisted within createLogger's scope function logMessage(msg) { console.log(`[${prefix}] ${msg}`); } } ``` `createLogger` is hoisted to the module scope. Inside it, `logMessage` is hoisted to `createLogger`'s scope. The returned function can call `logMessage` safely even though it appears after the `return` statement. ### The `var` loop closure bug and the fix ```javascript // Classic interview trap function withVar() { for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } } withVar(); // 3, 3, 3 - i is shared across all iterations // Fix with let function withLet() { for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); } } withLet(); // 0, 1, 2 - each iteration gets its own i ``` One of the most common hoisting questions in JavaScript interviews. `var` hoists `i` to the function scope, so all three closures capture the same variable. By the time the callbacks run, the loop has finished and `i` is `3`. `let` creates a fresh binding per iteration.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.