JavaScript Scope Explained: Difference Between Var and Let - Owlcation - Education
Updated date:

JavaScript Scope Explained: Difference Between Var and Let

Isaac has a passion for coding, and has knowledge on web design, web development, and programming in general.

One of the challenges JavaScript programmers getting started with ES6 struggle with has to do with the difference between var and let. Both are keywords in JavaScript used to declare variables. Before the let statement was introduced in ES2015, which is what we refer to as ES6, var was the standard way of declaring variables. The availability of a new statement to declare non-constant variables later on therefore came with a bit of confusion.

differences-between-var-and-let-explained-javascript-for-beginners
var firstVariable = "I'm first!" // Declared and initialized
let secondVariable; // Simply declared.

Variables declared both ways can store values, be it primitive values or objects, and may be initialized when created. They may also be null or undefined.

var firstVariable; // Value is undefined.
let secondVariable = null; // This is valid as well.

But now you want to know: what is the difference between var and let? The answer is scope.

Understanding Scope In JavaScript

For starters, JavaScript scope refers to the level of accessibility of variables. In other words, scope determines whence variables are visible in our script. Let’s see an example of what scope is about, with actual code:

var myNumber = 10;

function addTwo(userNum) {
    var numberTwo = 2;
    return numberTwo + userNum; 
}

function subtractTwo(userNum) {
    return userNum - numberTwo;
}

console.log(addTwo(myNumber)); // 12

console.log(subtractTwo(myNumber)); // ReferenceError: numberTwo is not defined

Let’s go through the JavaScript example above. We first create a variable called myNumber and assign the value 10 to it. We then create the function addTwo(), which takes a parameter, userNum. Inside of that function, we declare the variable numberTwo and initialize it with the value 2. We proceed to add it to the value of our function’s parameter and return the result.

In a second function called subtractTwo(), we expect to receive a number as a parameter, from which we intend to deduct 2 and return the result. But we are doing something wrong here. When deducting 2 from the parameter’s value, we use the numberTwo variable that we declared and initialized in our addTwo() function. By doing so, we are incorrectly assuming that the numberTwo variable is accessible outside its function, when in fact it is not.

Notice that this eventually causes our code to have an error. In Line 12, we pass in the value 10, which is stored in our global variable myNumber, to our addTwo() function. The output in the console is as expected, as we get the number 12.

In Line 14, however, when we try to output the result of our subtraction, we get what is known as a reference error in JavaScript. Try running this code in a text editor of your choice and opening your browser console to see the output. You will see an error message pointing to Line 9 of our script: Uncaught ReferenceError: numberTwo is not defined.

The reason for this is clearly stated. The numberTwo variable that we are trying to access in Line 9 is inaccessible. It is thus not recognized, and because we have not declared any variable with the same name in our subtractTwo() function, there is no valid location in memory to reference, hence the error.

That is how scope works in JavaScript. We would have gotten the same erroneous result even if we used the let keyword instead of var. The takeaway here is that scope is the context of execution. Every JavaScript function has its own scope; therefore, variables declared in a function may only be visible and used within that function. Global variables, on the other hand, may be accessed from any part of the script.

Understanding Scope Hierarchy

When writing code in JavaScript, we need to remember that scopes can be hierarchically layered. This means that one scope, or a parent scope, can have yet another scope, or child scope, within it. Variables from the parent scope may be accessed from the child scope, but not the other way around.

var globalVariable = "Hi from global!"; // This is accessible everywhere within this script.

function parentScope() {
    var accessEverywhere = "Hi from parent"; // This is accessible everywhere within the parentScope function.

    function childScope() {
        var accessHere = "Hey from child";
        console.log(accessHere); // This is accessible within this childScope function only.
    }

    console.log(accessEverywhere); // Hi from parent

    console.log(accessHere); // Uncaught ReferenceError: accessHere is not defined
}

parentScope();
console.log(globalVariable);

The JavaScript example above provides an illustration of the hierarchical nature of scopes. For now, we are only using the var keyword. We have one global variable at the top of our script, which we should be able to access anywhere within it. We then have a function called parentScope(), which contains the local variable accessEverywhere.

The latter is visible anywhere within the function. Finally, we have another function called childScope(), which has a local variable called accessHere. As you might have guessed by now, that variable can only be accessed in the function within which it is declared.

But our code generates an error, and that is because of a mistake in Line 13. On Line 16 when we call the parentScope() function, the console logging statements in both Line 11 and Line 13 get executed. Though the accessEverywhere variable gets logged without any issue, the execution of our code stops when we try to output the value of the accessHere variable in Line 13. The reason for that is that the variable in question was declared in the childScope() function and is therefore not visible to the parentScope() function.

Thankfully, there is an easy solution to that. We simply need to call the childScope() function without our parentScope() function definition.

var globalVariable = "Hi from global!"; // This is accessible everywhere within this script.

function parentScope() {
    var accessEverywhere = "Hi from parent"; // This is accessible everywhere within the parentScope function.

    function childScope() {
        var accessHere = "Hey from child";
        console.log(accessHere); // This is accessible within this childScope function only.
    }

    childScope(); // Call the function instead of accessing its variable directly
    
    console.log(accessEverywhere); // Hi from parent
}

parentScope();
console.log(globalVariable);

Here, I am saving this code into a JavaScript file called tutorialscript.js and linking it to an index.html file on my local server. When I run my script, I see the following in my Chrome console.

All the variable values we are expecting are being logged to the console without any errors.

All the variable values we are expecting are being logged to the console without any errors.

We now understand how scope in JavaScript works. Let’s concentrate once again on the var and let keywords. The main difference between these two is that variables declared with var are function scoped, whereas those declared with let are block scoped.

You have seen examples of function-scoped variables above. Block scoped, nevertheless, means that the variable is only visible within the block of code within which it is declared. A block can be anything within curly brackets; take if/else statements and loops, for instance.

function fScope() {
    if (1 < 10) {
        var hello = "Hello World!"; // Declared and initialized inside of a block
    }
    console.log(hello); // Available outside the block. It is function scoped.
}

fScope();

The piece of code above, with its comments, is self-explanatory. Let’s replicate it and make a couple of changes. In Line 3, we will use the let keyword, then try to access the hello variable in Line 4. You will see that our code will generate an error because of Line 6, as accessing a variable declared with let outside of its block scope is not allowed.

function fScope() {
    if (1 < 10) {
        let hello = "Hello World!"; // Declared and initialized inside of a block. Block scoped.
        console.log("The value is: " + hello); // Variable is visible within the block.
    }
    console.log(hello); // Uncaught ReferenceError: hello is not defined
}

fScope();

Should I use var or let?

Before ES6, there was no block scope in JavaScript; but its introduction helps in making one’s code more robust. Personally, I prefer to use let as it makes it easier for me to debug and fix unexpected behavior caused by reference errors.

When working on a large program, reducing the scope as best as you can is always a good recommendation. Having said that, if your script only consists of a dozen lines of codes, you probably should not worry too much about which keyword you use, so long as you know the difference between global scope, function scope and block scope in JavaScript and are able to avoid errors.