Back

JavaScript Variable Declarations: Understanding var, let, and const

JavaScript Variable Declarations: Understanding var, let, and const

Modern JavaScript development demands clarity in JavaScript variable declarations—yet many developers still struggle with choosing between var, let, and const. Understanding ES6 variable scope isn’t just about memorizing rules; it’s about writing code that communicates intent and prevents bugs before they happen.

Key Takeaways

  • Block scope (let/const) provides better variable containment than function scope (var)
  • The Temporal Dead Zone prevents using variables before initialization
  • const prevents reassignment but not mutation of objects and arrays
  • Modern JavaScript favors const by default, let when needed, and avoids var

Understanding JavaScript’s Three Declaration Keywords

JavaScript offers three ways to declare variables, each with distinct behaviors around scope, hoisting, and reassignment. Your choice signals different intentions to both the JavaScript engine and other developers reading your code.

Block Scope vs Function Scope

The fundamental difference between modern ES6 variable scope and legacy patterns lies in how variables are contained:

function processData() {
    if (true) {
        var x = 1;  // Function-scoped
        let y = 2;  // Block-scoped
        const z = 3; // Block-scoped
    }
    console.log(x); // 1 (accessible)
    console.log(y); // ReferenceError
    console.log(z); // ReferenceError
}

With var, variables leak out of blocks but remain within functions. Both let and const respect block boundaries—any curly braces create a new scope, including those in loops, conditionals, and plain blocks.

The Temporal Dead Zone Explained

JavaScript variable declarations with let and const are hoisted but remain uninitialized until their declaration is reached. This creates the Temporal Dead Zone (TDZ):

console.log(myVar);   // undefined (hoisted, initialized)
console.log(myLet);   // ReferenceError (TDZ)
console.log(myConst); // ReferenceError (TDZ)

var myVar = 1;
let myLet = 2;
const myConst = 3;

The TDZ prevents the subtle bugs that var’s behavior often caused. You can’t accidentally use a variable before it’s properly initialized—the engine throws an error immediately.

Reassignment vs Mutation

A critical distinction many developers miss: const prevents reassignment, not mutation:

const user = { name: 'Alice' };
user.name = 'Bob';          // Allowed (mutation)
user = { name: 'Charlie' }; // TypeError (reassignment)

const scores = [95, 87];
scores.push(91);            // Allowed
scores = [100, 100];        // TypeError

For true immutability, combine const with Object.freeze() or immutable data libraries. The declaration keyword alone doesn’t make your data immutable—it makes the binding immutable.

Modern Best Practices for Variable Declarations

JavaScript codebases today follow a clear hierarchy:

  1. Default to const: Use for values that won’t be reassigned. This includes most variables, even objects and arrays you’ll mutate.

  2. Use let for reassignment: Loop counters, accumulator variables, and values that genuinely need rebinding.

  3. Avoid var in new code: There’s no modern use case where var provides benefits over let.

Modern tooling enforces these patterns. ESLint’s no-var rule and prefer-const rule catch violations automatically. TypeScript treats ES6 variable scope rules as fundamental, making block scope the default mental model.

Framework Considerations

React hooks depend on JavaScript variable declarations behaving predictably:

function Counter() {
    const [count, setCount] = useState(0); // const for the binding
    
    // Wrong: Can't reassign
    // count = count + 1;
    
    // Right: Use the setter
    setCount(count + 1);
}

Vue’s Composition API and other modern frameworks follow similar patterns, where const dominates because reactivity systems handle updates through methods, not reassignment.

Why Legacy Code Still Uses var

You’ll encounter var in older codebases for historical reasons. Before ES6 (2015), it was the only option. Migration requires careful testing because:

  • Changing var to let in loops can fix bugs or introduce them, depending on closure behavior
  • Some legacy code deliberately exploits var’s function-scope hoisting
  • Older build tools might not transpile ES6 variable scope correctly

When refactoring, use automated tools like jscodeshift with thorough test coverage. Don’t manually convert thousands of declarations—let tooling handle the mechanical changes while you verify behavior.

Conclusion

JavaScript variable declarations in modern code follow a simple rule: use const by default, let when you need reassignment, and never var. This isn’t about following trends—it’s about writing code that clearly expresses intent and leverages ES6 variable scope to prevent entire categories of bugs. The Temporal Dead Zone, block scoping, and reassignment rules aren’t obstacles—they’re guardrails that guide you toward more maintainable JavaScript.

FAQs

Yes, const only prevents reassigning the variable itself, not modifying the contents. You can add, remove, or change properties in objects and elements in arrays declared with const. The variable always points to the same object or array in memory.

You'll get a ReferenceError due to the Temporal Dead Zone. Unlike var which returns undefined when accessed before declaration, let and const variables exist in an uninitialized state until the code reaches their declaration line.

Only with careful testing. While generally beneficial, changing var to let in loops can alter closure behavior. Use automated refactoring tools with comprehensive test coverage rather than manual changes to avoid introducing bugs.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay