Today I want to take you on an adventure where we'll discover the key differences between var variables and let variables. Many introductions to coding (in Javascript) usually start with var variables, but I believe you should move to let variables as soon as you can. You'll have fewer bugs, and a better programming experience. Let's do this!

First, I want to introduce the concept of Scope. A definition I love to use goes like this:

Scope is the area of code where you can access a symbol

A symbol, in this context, can be a variable or a function. We say that a symbol is 'within scope' when you can safely use it without any errors. For example:

var variable = 'Hello'; 
// We can log variable here because is within scope
console.log(variable);

Things become a little bit more interesting when you are dealing with functions:

console.log(variable); // This will not work

function thisIsAFunction() {
    var variable = 1; 
    console.log(variable); // This will work
}

console.log(variable); // This will not work

thisIsAFunction();

Notice how the logs outside of the function will not work (i.e. the name variable is not within scope), but the log inside the function will work.

Why is that?

var variables use what we know as Function-based scope. If you declare a var variable inside a function, the variable will be within scope everywhere inside the function.

Hoisting is going to slightly complicate where you can access your variable. In general, It's safer to use your var variable only after you declare it. We'll talk about hoisting in an upcoming article, so get excited!

Now, let's add an if statement inside our function:

console.log(variable); // This will not work

function thisIsAFunction() {
    if(true === true) { // This is a simple if statement to avoid confusion
        var variable = 1;
        console.log(variable); // This works
    }
    console.log(variable); // This works
} 

console.log(variable); // This will not work

Our var variable is only within scope inside the function where it was declared. Notice how even though variable was declared inside an if statement, you can still use it outside the statement. That's function-based scope at play!

Now let's go full power and change our var variable into a let variable:

console.log(variable); // This will not work

function thisIsAFunction() {
    if(true === true) { // This is a simple if statement to avoid confusion
        let variable = 1; 
        console.log(variable); // This works
    }
    console.log(variable); // This will not work
} 

console.log(variable); // This will not work

thisIsAFunction();

Notice how as soon as we change var to let, one more log stops working.

What's the difference between the log in line 6 and the log in line 8?

The difference is that they are in different blocks. If you are thinking 'Well, what's a block?, I've got you covered my friend.

If you want to get super technical, a block is a "lexical structure of source code which is grouped together", but I like to introduce the concept as:

Block - whatever is contained inside curly brackets: functions, loops, if statements, etc.

Objects are a funny exception to the definition I just gave about curly brackets, but that's the only exception I know of.

Let's think about the blocks we have in our current function

console.log(variable);

function thisIsAFunction() { // Start of block A
    if(true === true) { // Start of block B
        let variable = 1; 
        console.log(variable);
    } // End of block B
    console.log(variable); 
} // End of block A

console.log(variable);

thisIsAFunction();

Since variable was defined inside block B, it can only be used inside block B (here comes the important point) and inside every block contained within B.

Technically Speaking, variable is inside block A Right?. Why is the console.log breaking?

Great point. Block B is inside Block A, so technically variable was declared inside block A.

However, the scope resolution rule let uses is going to look for the closest enclosing block (that would be block b) and allow you to use the variable everywhere inside that block and every other block within it.

Blocks containing that 'closest enclosing block' won't have access to variable

So what would happen if we move variable before the if statement?

console.log(variable);

function thisIsAFunction() { // Start of block A
    let variable = 1; 
    if(true === true) { // Start of block B
        console.log(variable);
    } // End of block B
    console.log(variable); 
} // End of block A

console.log(variable);

thisIsAFunction();

It would be accessible everywhere inside block A as that would be the closest enclosing block. Since block B is inside block A, it'll also be accessible inside block B.

blocks, blocks everywhere meme

This seems like adding a bunch of rules on top of var. What's the point?

Great question! Blocks tend to be smaller than functions. Basing our scoping rules on smaller scopes will mean that a variable name is 'available' on a smaller area of code.

A smaller area of code means it is less likely to change that variable by mistake.

we don't make mistakes, we make happyc bugs meme

I get it! Anything 'else?

One last thing. let also has built-in protections to avoid re-declaring the same variable by mistake.

let kali = 'is cute';

// LOTS OF CODE IN BETWEEN

let kali = 2; // This will break

As soon as you try to re-declare the name kali, you'll get an error along the lines of redeclaration of identifier kali.

On the other hand:

var kali = 'is cute';

// LOTS OF CODE IN BETWEEN

var kali = 2; // This works just fine

var variables will let you re-declare the same name over and over without any complaint. You could end up overstepping on someone else's (or even your own) variables without even realizing it. That's another big reason to use let variables as much as you can.

In Summary

  • Scope is the area of the code where you can access a name.
  • var variables use function-based scope. They can be used inside the function where they are defined.
  • let variables use block-based scope. They can be used inside the block where they are defined.
  • let variables will not let you re-declare the same name.