JS let vs var vs const Explained

Mateen Kiani

Mateen Kiani

Published on Tue Jul 01 2025·5 min read

js-let-vs-var-vs-const-explained

JavaScript variables are the building blocks of any script you write, but their behavior can change dramatically depending on how you declare them. Most developers learn about var, let, and const early on, yet the subtle differences in scope, hoisting, and mutability often cause confusion in larger projects. Have you ever wondered why using var sometimes leads to unexpected behavior compared to let or const?

The answer lies in understanding how each keyword handles scope, hoisting, and assignment. Grasping these differences helps you write clearer code, avoid sneaky bugs, and make informed decisions when choosing the right declaration for your needs.

var Scope Drawbacks

Older JavaScript code uses var almost exclusively. However, var declarations are function-scoped rather than block-scoped, which can introduce unexpected leaks. For example:

function example() {
if (true) {
var x = 10;
}
console.log(x); // 10, even though x was inside the if block
}

Here, var doesn’t respect the if block, so x is available throughout the function. In large functions, this can lead to accidental overwrites and hard-to-find bugs. Also, var allows redeclaration:

var name = 'Alice';
var name = 'Bob';
console.log(name); // Bob

Redeclaring a variable without an error can mask typos or logic mistakes. Despite these drawbacks, var still works in older environments. If you need to support legacy browsers without transpilation, var might be your only choice. But in modern code, avoid mixing var with let or const to keep behavior predictable.

Tip: If you see var in code, refactor to let or const when possible to leverage block scoping and clearer intent.

let Block Behavior

Introduced in ES6, let brings block scope to JavaScript. Variables declared with let only live inside the nearest enclosing {}. This change greatly reduces accidental leaks:

for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs 0, 1, 2 as expected

With var, that same loop would log 3, 3, 3, because var is function-scoped. Another advantage of let is that it prevents redeclaration in the same scope:

let age = 25;
let age = 30; // SyntaxError: Identifier 'age' has already been declared

This behavior catches mistakes early. Because let does not allow redeclaration, your code signals intent clearly: you plan to reassign the variable, but not create it twice.

However, let variables are mutable. You can change their value freely:

let count = 1;
count = 2;

Use let when a value needs to change over time, such as counters, flags, or accumulators.

const Immutable Bindings

Const also offers block scope, but with a twist: the binding is read-only. Once you assign a value, you can’t reassign that variable:

const maxItems = 5;
maxItems = 10; // TypeError: Assignment to constant variable.

That doesn’t mean the value itself is frozen. For objects and arrays, const prevents rebinding but not mutation:

const user = { name: 'Alice' };
user.name = 'Bob'; // Allowed
user = {}; // Error

Use const by default. It shows your intent clearly: this variable won’t be reassigned. When you need mutability, switch to let. This practice leads to safer, more predictable code.

Tip: Default to const. Only pick let when you know the variable’s value must change.

Hoisting Comparison Table

All three declarations—var, let, and const—are hoisted, but they behave differently at runtime. Here’s how they compare:

KeywordHoisted to TopTemporally Dead ZoneDefault Value
varYesNoundefined
letYesYesReferenceError
constYesYesReferenceError
console.log(aVar); // undefined
console.log(aLet); // ReferenceError
console.log(aConst); // ReferenceError
var aVar = 1;
let aLet = 2;
const aConst = 3;

The “Temporally Dead Zone” between the start of the scope and the declaration means let and const throw errors if accessed too early. This helps catch mistakes before they run.

Real-World Examples

Consider a form handler that tracks submission status:

function submitForm(data) {
let status = 'pending';
try {
// send data...
status = 'success';
} catch (e) {
status = 'error';
}
console.log(status);
}

Here, let works well since status changes. If status were const, you’d get an error. On the other hand, configuration values can use const:

const API_ENDPOINT = 'https://api.example.com';

That binding should never change. Mixing var here could accidentally override your endpoint. Also, be mindful of global leaks: var declares variables on the global object when used at the top level, while let and const do not. To learn more about global scope pitfalls, check out are-javascript-variables-global.

Best Practices Summary

• Default to const for all variables.
• Switch to let when you need to reassign.
• Avoid var entirely in modern code.
• Keep your scopes small—use blocks to contain logic.
• Name variables clearly to reflect mutability (e.g., finalCount for a const counter).

These guidelines help maintain consistency across your codebase and reduce time spent chasing scope-related bugs.

Conclusion

Understanding var, let, and const ensures that your JavaScript code behaves predictably and is easy to maintain. Var’s function scope can lead to accidental leaks, while let and const offer block-scope safety. Const signals unchanging bindings, and let allows controlled mutability. By defaulting to const, falling back to let when necessary, and avoiding var, you’ll write clearer, more reliable code. Keep these rules in mind in every project, and your future self—and your teammates—will thank you.


Mateen Kiani
Mateen Kiani
kiani.mateen012@gmail.com
I am a passionate Full stack developer with around 3 years of experience in MERN stack development and 1 year experience in blockchain application development. I have completed several projects in MERN stack, Nextjs and blockchain, including some NFT marketplaces. I have vast experience in Node js, Express, React and Redux.