2 examples to understand the difference between var and let


There are countless articles related to the differences between let, const, and var. However, when it comes to var the usual conclusion is to just stop using it. I’m not here to debunk that or suggest a new way of doing things. But the interesting part is not whether it is abandoned or not, but rather knowing why. Why is that considered a good practice?

Well, the most important reason is the lexical scope.

You see, lexical scope and var had a good life together. But one day… something changed. Lexical scope sought new horizons while var remained in its comfort zone.

What you need to know

When talking about var we always have to talk about the past. In the old days, before the arrival of ECMAScript 6, var was the only way to declare variables. Well, you might say, “I already knew that, it’s one of the first things they tell you when they introduce let and const” – which is true. But what they don’t always tell you is that back then, lexical scope was divided only into two: global scope and local scope.

“Oh my god! What on earth are those scopes?”

Well, if you don’t know that, you shouldn’t be reading this. Go quickly to learn about lexical scope and then come back.

Do you know now? The answer should be yes, and therefore you already know the following information.

ScopesECMAScript 5 and earlierECMAScript 6 onwards
block scopeNoYes
local scopeYesYes
global scopeYesYes
Keywords for declaring variablesECMAScript 5 and earlierECMAScript 6 onwards
letNoYes
constNoYes
varYesYes

With this information in mind, you will understand the problem. Previously there was no block scope, and therefore var was never designed to take that functionality into account.

Let’s see a couple of examples with var and let/const.

let and const

You can look at let and const as the successors of var. Being the most recent ones it’s ideal to learn how they work first.

Of course, unlike var both let and const behave properly with any of the 3 scopes.

Example 1

In the following code you will see a global variable and a local variable declared::

**Note: For the examples you will see, I will only use let. However, I could have used const and it would behave in the same way. When talking about scope they share the same behavior.

let outputPath = '../../resources/output'

function createNiceDocument() {
    let niceOptions = {
        margin: 'perfect',
        textColor: 'darkGray',
    }
    console.log('outputPath: localScope = ', outputPath)
    console.log('niceOptions: localScope = ', niceOptions)
    //TODO: createDocument
}

createNiceDocument()
console.log('outputPath: globalScope = ', outputPath)
console.log('niceOptions: globalScope = ', niceOptions)

Upon execution, this code returns the following result:

outputPath: localScope =  ../../resources/output
niceOptions: localScope =  { margin: 'perfect', textColor: 'darkGray' }
outputPath: globalScope =  ../../resources/output

...\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:15
console.log('niceOptions: globalScope = ', niceOptions)
                                           ^
ReferenceError: niceOptions is not defined

As one would expect, the global and local scope are respected perfectly.

  • niceOptions – the local variable
    • It can only be accessed within the local scope of the createNiceDocument function.
    • It throws an error if you try to call it in the global scope.
  • outputPath – the global variable
    • It is called correctly regardless of whether you call it from the global scope or the local scope.

Example 2

Now. An example adding an if block:

let outputPath = '../../resources/output'

function createNiceDocument() {
    let niceOptions = {
        margin: 'perfect',
        textColor: 'darkGray',
    }

    //Let's not make it complicated. The code will always enter this if statement.
    if(true) {
        let pagesNumber = 3
        console.log('niceOptions: blockScope? = ', niceOptions)
        console.log('pagesNumber: blockScope? = ', pagesNumber)
    }
    
    console.log('niceOptions: localScope = ', niceOptions)
    console.log('pagesNumber: localScope = ', pagesNumber)
    //TODO: createDocument
}


createNiceDocument()

It works as well:

  1. pagesNumber – the block variable
    • It can only be accessed within the block scope of the if statement inside the createNiceDocument function.
    • It throws an error if it is called either in the local scope of the function or in the global scope.
  2. niceOptions – the local variable
    • It is called correctly regardless of whether it is called from the local scope or the block scope.
niceOptions: blockScope? =  { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: blockScope? =  3
niceOptions: localScope =  { margin: 'perfect', textColor: 'darkGray' }
...\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:17
    console.log('pagesNumber: localScope = ', pagesNumber)
                                              ^

ReferenceError: pagesNumber is not defined

var

Now it’s var's turn. How will the code behave in the previous examples if we replace let with var?

Example 1

First, let’s evaluate the scenario with the global and local scope:

var outputPath = '../../resources/output'

function createNiceDocument() {
    var niceOptions = {
        margin: 'perfect',
        textColor: 'darkGray',
    }
    console.log('niceOptions: localScope = ', niceOptions)
    //TODO: createDocument
}

createNiceDocument()
console.log('outputPath: globalScope = ', outputPath)
console.log('niceOptions: globalScope = ', niceOptions)
outputPath: localScope =  ../../resources/output
niceOptions: localScope =  { margin: 'perfect', textColor: 'darkGray' }
outputPath: globalScope =  ../../resources/output

..\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:15
console.log('niceOptions: globalScope = ', niceOptions)
                                           ^
ReferenceError: niceOptions is not defined

If you pay attention, there is no difference at all compared to the example with let and const. In these cases, you can use any of the 3 keywords interchangeably.


Example 2

Now, what happens in the scenario with the if statement?

var outputPath = '../../resources/output'

function createNiceDocument() {
    var niceOptions = {
        margin: 'perfect',
        textColor: 'darkGray',
    }

    //Let's not make it complicated. The code will always enter this if statement.
    if(true) {
        var pagesNumber = 3
        console.log('niceOptions: blockScope? = ', niceOptions)
        console.log('pagesNumber: blockScope? = ', pagesNumber)
    }
    
    console.log('niceOptions: localScope = ', niceOptions)
    console.log('pagesNumber: localScope = ', pagesNumber)
    //TODO: createDocument
}


createNiceDocument()

If we stick to the idea that we should have a block scope, pagesNumber should only be accessible within our if statement. Therefore, this code should throw an error:

niceOptions: blockScope? =  { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: blockScope? =  3
niceOptions: localScope =  { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: localScope =  3

…But that’s not the case. This is precisely because of what I mentioned before: var doesn’t the take block scope into account and treats it as if it were a local scope. The way it achieves this is through a process called hoisting. (This is also an important topic, so make sure to note it down to learn about it later.)


This kind of behavior is the reason why it is recommended to use let/const instead of var.

Regardless. there are still developers that see some value in using a var. Though more than anything it is only used as a visual way to point that a variable is global.

I personally think this is a good idea. A simple visual way to identify a certain pattern on the codebase. But on the other hand I’m not radical about it either. You’ll be able to form you own opinion with experience.

As for me I’m happy as long as I don’t see var declarations inside a function or even worse.. inside an if, while or for statement.

BONUS

I think that a this point you should already know the difference between let and const. Reading about Lexical Scope without knowing that is like opening a book and jumping ahead to chapter 10 instead of starting at 1.

But in the rare case you don’t know or forgot. I’ll remind you.

  • let
    • You can modify the value after it has been initialized.
  • const
    • You cannot modify the value after it has been initialized.