JavaScript var vs let: Why you shouldn't use var

Dec 19, 2024 · 8 min

I've been wrestling with JavaScript variable declarations for years, and let me tell you - the introduction of let and const in ES6 was a game changer! Before ES6, we were stuck with var and all its... quirks. I remember spending hours debugging issues that turned out to be scoping problems with var.

If you've ever found yourself scratching your head wondering why your loop variables behave weirdly, or why your variables seem to "leak" out of blocks where you didn't expect them to - you're not alone. The difference between var and let isn't just academic; it's the difference between code that works predictably and code that drives you crazy.

Why This Matters (Trust Me, It Does!)

The keywords var and let might seem interchangeable at first glance, but they differ in ways that can make or break your code. The main culprits? Scoping rules and hoisting behavior.

I learned this the hard way when I spent an entire afternoon debugging what I thought was a complex logic error, only to discover it was a simple scoping issue with var. Let's make sure you don't have to go through the same pain! 😅

1. Scoping: The Most Critical Difference

var: The Function-Scoped Wildcard

So here's where var gets interesting (and by interesting, I mean frustrating). Variables declared with var are scoped to the nearest function - not the block where you declared them. If you declare them outside any function, they become global variables. This means they're accessible throughout the entire function, completely ignoring those curly braces you thought would contain them.

function exampleVar() {
  var x = 10;
  if (true) {
    var x = 20; // This re-declares the same 'x' variable
    console.log(x); // Output: 20
  }
  console.log(x); // Output: 20 (The value of 'x' was updated)
}
exampleVar();

This behavior can bite you in the most unexpected places. I've lost count of how many times I've seen this classic loop gotcha:

for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Output: 3, 3, 3 (Wait, what?!)
  }, 100);
}

Yeah, that's not what you expected, right? 😅

let: The Well-Behaved Block-Scoped Hero

Now this is where let shines! Variables declared with let are scoped to the nearest enclosing block (those curly braces {} actually mean something now). They stay exactly where you put them - no wandering around the function like a lost tourist.

function exampleLet() {
  let y = 10;
  if (true) {
    let y = 20; // This declares a *new* 'y' variable within the if block
    console.log(y); // Output: 20
  }
  console.log(y); // Output: 10 (The 'y' from the outer scope is preserved)
}
exampleLet();

And that same loop that gave us headaches with var? With let, it just works:

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i); // Output: 0, 1, 2 (Finally! 🎉)
  }, 100);
}

This is the behavior we actually wanted all along!

2. Hoisting: The Magic Trick You Didn't Ask For

var: The "Helpful" Hoist

Here's another fun quirk of var - it gets "hoisted" to the top of its function or global scope. Basically, JavaScript says "Oh, you declared a variable down there? Let me just... move that to the top for you!" But here's the catch - only the declaration gets moved, not the value.

console.log(a); // Output: undefined (not an error!)
var a = 5;
console.log(a); // Output: 5

// The above is effectively interpreted as:
// var a; // hoisted and initialized to undefined
// console.log(a);
// a = 5;
// console.log(a);

let: The Temporal Dead Zone (Sounds Scary, But It's Actually Good!)

Now let also gets hoisted, but JavaScript doesn't initialize it with undefined like it does with var. Instead, if you try to access a let variable before its declaration, JavaScript throws a tantrum (a ReferenceError to be precise). This period between hoisting and declaration is dramatically called the "Temporal Dead Zone" - sounds like something from a sci-fi movie, right?

// console.log(b); // Throws ReferenceError: Cannot access 'b' before initialization
let b = 5;
console.log(b); // Output: 5

3. Re-declaration Behavior

var: Allows Re-declaration

var name = "John";
var name = "Jane"; // No error, just reassigns
console.log(name); // Output: "Jane"

let: Prevents Re-declaration

let name = "John";
// let name = "Jane"; // SyntaxError: Identifier 'name' has already been declared

Real-World Examples (Where the Rubber Meets the Road)

Example 1: The Classic Event Listener Nightmare

Let me share a scenario that has caused countless developers to question their life choices. You want to add event listeners to multiple buttons, and you want each to know its own index.

The var approach (prepare for disappointment):

const buttons = document.querySelectorAll('button');

for (var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function() {
    console.log('Button', i, 'clicked'); // Always logs the final value of i
  });
}

The let approach (sanity restored!):

const buttons = document.querySelectorAll('button');

for (let i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function() {
    console.log('Button', i, 'clicked'); // Actually logs the right button! 🎯
  });
}

And just like that, your code works as expected. No closures, no workarounds, no pulling your hair out.

Example 2: Variable Isolation in Blocks

function processData(condition) {
  if (condition) {
    var varVariable = "I'm function-scoped";
    let letVariable = "I'm block-scoped";
  }
  
  console.log(varVariable); // Works: "I'm function-scoped"
  // console.log(letVariable); // ReferenceError: letVariable is not defined
}

My Take on Modern JavaScript (What I Wish Someone Told Me Earlier)

1. Just Say No to var (Most of the Time)

Here's my rule of thumb that has saved me countless debugging hours:

  • Use const for values that won't change (which is most of them, honestly)
  • Use let when you actually need to reassign the variable
  • Pretend var doesn't exist in modern JavaScript (your future self will thank you)

2. Block Scope Benefits

Block scoping makes code more predictable and easier to debug:

function calculateTotal(items) {
  let total = 0;
  
  if (items.length > 0) {
    let tax = 0.08; // Only available in this block
    total = items.reduce((sum, item) => sum + item.price, 0);
    total += total * tax;
  }
  
  // tax is not accessible here - clear separation of concerns
  return total;
}

3. Loop Variable Best Practices

// Good: Using let in for loops
for (let i = 0; i < array.length; i++) {
  // Each iteration has its own 'i'
}

// Good: Using const when the loop variable doesn't change
for (const item of array) {
  // Each iteration has its own 'item'
}

Migration Strategy

If you're working with legacy code that uses var, here's a safe migration approach:

  1. Replace var with let for variables that change
  2. Replace var with const for variables that don't change
  3. Test thoroughly - scoping changes might affect behavior
  4. Use a linter like ESLint to catch potential issues

Wrapping Up (The TL;DR Version)

Look, I get it - variable declarations might not be the most exciting topic in the world. But trust me, understanding the difference between var and let will save you from those 2 AM debugging sessions where you're questioning everything you know about JavaScript.

Here's what let (and const) brought to the party:

  • Block scoping that actually makes sense - variables stay where you put them
  • The Temporal Dead Zone - which sounds scary but prevents sneaky bugs
  • No accidental re-declarations - JavaScript won't let you shoot yourself in the foot
  • Predictable loop behavior - finally!

My Final Thoughts

var isn't evil - it's just... old-school. Like using Internet Explorer or manually writing CSS vendor prefixes. It works, but we have better tools now.

In my experience, switching to let and const made my code more predictable and my debugging sessions shorter. And honestly, shorter debugging sessions mean more time for coffee ☕ and actually building cool stuff.

So here's my advice: embrace let and const. Your code will be clearer, your bugs will be fewer, and you'll look like you know what you're doing (even when you're still figuring it out, like all of us are).

Happy coding! 🚀

PS: If you found this helpful, I'd love to hear about your own var vs let horror stories in the comments. We've all been there!

> comment on twitter
> cd . .
If you don't take risks, you can't create a future. ~ Monkey D. Luffy
2026