How can you avoid problems related to hoisting?
TL;DR
Use const by default and let when reassignment is required, keep declarations close to their first use, and never read a binding before it is initialized. let and const are still hoisted—their bindings exist from the start of the block—but they remain in the temporal dead zone until evaluation reaches the declaration. Static-analysis rules such as no-use-before-define and no-undef catch the risky patterns.
// Use let or constlet x = 10;const y = 20;console.log(x, y); // Output: 10 20// Declare functions before calling themfunction myFunction() {console.log('Hello, world!');}myFunction(); // Output: 'Hello, world!'
How can you avoid problems related to hoisting?
Understand hoisting
“Hoisting” describes the observable result of JavaScript creating bindings while it instantiates a scope before evaluating its statements. The engine does not physically move source code.
Use let and const instead of var
let and const are block-scoped. Their bindings are hoisted to the start of the block but stay uninitialized in the temporal dead zone, so an early access throws instead of silently producing undefined.
console.log(x); // undefined. (Binding initialized before statements run)console.log(y); // ReferenceError: Cannot access 'y' before initialization. (TDZ)console.log(z); // ReferenceError: Cannot access 'z' before initialization. (TDZ)// Avoid using varvar x = 10; // Function- or script-scoped; assignment happens here// Use let or constlet y = 20; // Block-scoped; initialized hereconst z = 30; // Block-scoped; initialized here
Keep declarations close to their first use
Declare and initialize a binding before its first use, preferably close to the code that needs it. Moving every declaration to the top can widen its apparent lifetime and separate it from its purpose.
function example() {let a = 1;const b = 2;// Now use a and bconsole.log(a + b);}example(); // Output: 3
Declare functions before calling them
Function declarations are initialized during scope setup; function expressions are evaluated in place. Keeping either form before its first call usually makes control flow easier to read, even when an earlier call to a declaration would be valid.
// Function declaration (hoisted)function myFunction() {console.log('Hello, world!');}myFunction(); // No issues here// Function expression (created when this declaration is evaluated)const anotherFunction = function () {console.log('Hello again!');};anotherFunction(); // No issues here
Avoid using undeclared variables
Using undeclared variables can lead to unexpected behavior due to hoisting. Always declare your variables before using them.
// Avoid thisfunction badExample() {x = 10; // ReferenceError in strict modeconsole.log(x);}// Do this insteadfunction goodExample() {let x = 10; // x is declaredconsole.log(x);}goodExample();badExample();