Var vs Let vs Const
Tags: javascript, bestpractices
Published: 2018-10-17 15:47:15

---
title: Var vs Let vs Const
published: true
tags: ["javascript", "best practices"]
description: "In this post we examine the difference between var, let and const and then we go through some recommendations of when to use each one."
canonical_url: https://blog.nicolasmesa.co/posts/2018/08/var-vs-let-vs-const/
---

Cross-posted from my personal blog [https://blog.nicolasmesa.co](https://blog.nicolasmesa.co).

Hi there!

In this post, we’re going to talk about `javascript`. We’ll start by looking at some examples of `var`, `let` and `const` variable declarations and their properties. Then we’re going to go through my recommendations of when to use each one.

## var

`var` is the original way to do variable declaration in javascript. Here’s an example of how you can declare a variable using `var`:


```javascript
var myVariable = 10;
```


### var is function-scoped

Variables defined using `var` are function scoped. This means that the variable is available anywhere within its enclosing function. For example:


```javascript
function functionScopedExample() {
    var a = 10;

    if (a < 20) {
        var b = 15;
    } else {
        var c = 30;
    }

    console.log(a, b, c); // logs: 10 15 undefined
}
```

There are a few things happening in this example. First, `a` is available everywhere in this function (like in most programming languages). Second, `b` is also available everywhere even though we declared and assigned it inside an `if` block. Third, `c` is also available everywhere in the function (even though our execution path doesn’t go into the `else` statement)! One thing to note here is that the value of `c` is `undefined` (since the code in the `else` statement is not executed), but `c` was still declared. To understand why this happened, we’ll need to learn about Variable Hoisting.

Here is another example of _function-scoped_ variables:
 
```javascript
function functionScopedExample2() {
    var a = 10;

    function innerFunction() {
        var b = 20;
        console.log(a); // logs: 10
    }

    innerFunction();

    console.log(a); // logs: 10
    console.log(b); // error: Uncaught ReferenceError: b is not defined
}
```

This example shows that `a` is available in its enclosing function (`functionScopedExample2`). This includes other functions defined within it (for example the `innerFunction` function). `b`, however, can only be referenced from its enclosing function (`innerFunction`) and not from outside.

_Function-scoped_ declarations can have weird effects:


```javascript
function functionScopedExample3() {
    var myNumberArray = [];

    for (var i = 0; i < 10; i++) {
        setTimeout(function() {
            myNumberArray.push(i);
        }, 100);

    }

    // wait until all timeouts have executed.
    setTimeout(function() {
        console.log(myNumberArray); // logs: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
    }, 500);
}
```

The example above looks pretty weird. Most programmers would expect `myNumberArray` to have numbers from 0 - 9. Instead the array contains the number `10` 10 times! This happens because the variable `i` is in scope for the whole function (`functionScopedExample3`). By the time the functions in the `setTimeout` execute, `i` already has a value of `10`. One way to fix this issue is to create another function that receives `i` as an argument and executes the `setTimeout`:

 
```javascript
function fixedFunctionScopedExample3() {
    var myNumberArray = [];

    // a new scope is created every time this function is called.
    // As a result, newI is isolated.
    function executeTimeout(newI) {
        setTimeout(function() {
            myNumberArray.push(newI);
        }, 100);
    }

    for (var i = 0; i < 10; i++) {
        executeTimeout(i);
    }

    // wait until all timeouts have executed.
    setTimeout(function() {
        console.log(myNumberArray); // logs: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }, 500);
}
```

Later, we’ll see how to solve this problem by using `let` instead of `var`.

### Variable hoisting

When we define a variable using `var`, the variable declaration (not the assignment) is _hoisted_ up to the beginning of the function. After the _hoisting_ process, `functionScopedExample` (defined above) would end up looking like this:


```javascript
function varHoistingExample() {
    // variables are hoisted to the top and all of them start
    // with the value of undefined.
    var a;
    var b;
    var c;

    // a gets its value here.
    a = 10;

    if (a < 20) {
        // b gets its value here.
        b = 15;
    } else {
        // c never gets its value so it remains undefined.
        c = 30;
    }

    console.log(a, b, c); // logs: 10 15 undefined
}
```

_Hoisting_ can cause a lot of confusion. Take a look a the following example:

```javascript
function weirdHoisting() {
    color = 'yellow';
    var color;

    console.log('My favorite color is', color); // logs: My favorite color is yellow
}
```

Wait, what? How did that work? Remember, first the variables defined with `var` are _hoisted_ and then the function code is executed. In this example, the code after variable _hoisting_ would look something like this:

 
```javascript
function weirdHoisting() {
    // variable declaration is hoisted to the top of its enclosing function.
    var color;
    color = 'yellow';

    console.log('My favorite color is', color); // logs: My favorite color is yellow
}
```

Variable `color`’s declaration is _hoisted_ and thus prevents any reference errors.

Named function declarations are _hoisted_ as well (there are some [edge-cases if you’re doing conditionals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#Conditionally_created_functions)). Here’s an example:

```javascript
function functionHoisting() {
    hoisted(); // logs: I am hoisted
    notHoisted(); // error: Uncaught TypeError: notHoisted is not a function

    var notHoisted = function() {
        console.log('This will not work');
    }

    function hoisted() {
        console.log('I am hoisted');
    }
}
```

In this example, we see that the _hoisted_ function can be called before it is declared. The `notHoisted` variable, however, hasn’t been assigned the `function` by the time it is called. Note that the function doesn’t fail because the `notHoisted` name hasn’t been declared, but because it doesn’t point to a function (yet). After the hoisting process, this function would end up looking something like this:
 
```javascript
function functionHoisting() {
    // variable and function declaration are hoisted.
    var notHoisted;

    function hoisted() {
        console.log('I am hoisted');
    }

    hoisted(); // logs: I am hoisted
    notHoisted(); // error: Uncaught TypeError: notHoisted is not a function

    // function would be assigned here but we never reach this.
    notHoisted = function() {
        console.log('This will not work');
    }
}
```

## let and const

`let` is a newer way to define variables in javascript. Here’s an example of a variable definition using `let`:


```javascript
let myVariable = 10;
```

`const` is a way to define constants in javascript. Here’s an example of a constant defined using `const`:

```javascript
const myConstant = 200;
```

### let and const are block-scoped

`let` and `const` are both block-scoped. This means that the variable/constant is available anywhere in the block **after it has been declared**. `let`/`const` definitions are **NOT** _hoisted_. Let’s go through a few examples:


```javascript
function blockScopedExample() {
    let a = 10;

    if (a < 20) {
        let b = 15;
    } else {
        let c = 30;
    }

    console.log(a); // logs: 10
    console.log(b); // error: Uncaught ReferenceError: b is not defined

    // we would never reach this since the code fails in the line before
    // but this would be the output if it had been executed
    console.log(c); // error: Uncaught ReferenceError: c is not defined
}
```

This example shows that `b` and `c` are block-scoped. This is the reason why they’re not available outside of the `if`/`else` block. `a` was defined at the beginning of the function, making it available anywhere inside the function **block**. Declaring something with `let` at the beginning of a function is analogous to defining it with `var`.

Remember the example where our array ended up with the same values? Well, this problem is easily solvable by using `let` instead of `var` to define `i`:

```javascript
function blockScopedExample2() {
    let myNumberArray = [];

    // let makes the variable i block-scoped
    for (let i = 0; i < 10; i++) {
        setTimeout(function() {
            myNumberArray.push(i);
        }, 100);

    }

    // wait until all timeouts have executed.
    setTimeout(function() {
        console.log(myNumberArray); // logs: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }, 500);
}
```

Since the variable `i` is block-scoped instead of function-scoped, its value remains the same for the whole block execution.

Here’s another example showing that variables defined with `let` are not _hoisted_:
 
```javascript
function noHoisting() {
    color = 'yellow'; // error: Uncaught ReferenceError: color is not defined
    let color;

    // this is never reached.
    console.log('My favorite color is', color);
}
```

The output of this function is what most programmers coming from other languages expect (having to declare a variable before using it).

So far, every example (except for `blockScopedExample2`) we’ve covered using `let` would have the same outcome if we had used `const` instead. So, what does `const` give us?

### Constants

`const` is how constants can be declared in javascript. After a constant has been defined using `const`, the constant can’t have another value assigned. Here’s an example:
 
```javascript
function cantReassignConstants() {
    const myConstant = 10;
    myConstant = 9; // error: Uncaught TypeError: Assignment to constant variable
}
```

Constants defined with `const` also have to be assigned at declaration time (unlike `let` and `var`). For example, the following piece of code would fail during parsing (not at run-time).

 
```javascript
function constantsCantBeDeclaredAndNotDefined() {
    const myConstant;
    myConstant = 5;
}
```

If you try to define this function, you would see an error like this:

```
Uncaught SyntaxError: Missing initializer in const declaration
```

Since this error occurs during parsing, the declaration of the function fails and we can’t even call it.

```javascript
constantsCantBeDeclaredAndNotDefined(); // error: Uncaught ReferenceError: constantsCantBeDeclaredAndNotDefined is not defined
```

One thing to note about constants defined with `const` is that only the assignment is constant. The object assigned to a constant doesn’t become immutable. For example, the following code runs with no errors:


```javascript
function constDoesntMakeObjectsImmutable() {
    const myObjectConstant = {
        myKey: 'myValue'
    };

    myObjectConstant.myKey = 'something else';

    console.log('myObjectConstant.myKey =', myObjectConstant.myKey); // logs: myObjectConstant.myKey = something else
}
```

#### Object.freeze()

If you want to make the object immutable, you can use `Object.freeze()` before assigning the object:

```javascript
function freezingObject() {
    const myFrozenObject = Object.freeze({
        myKey: 'This value cannot be changed'
    });

    myFrozenObject.myKey = 'something else';

    console.log('myFrozenObject.myKey = ', myFrozenObject.myKey); // logs: myFrozenObject.myKey = This value cannot be changed
}
```

Even though the value of `myKey` didn’t change, the assignment didn’t fail either (it failed silently). If you want it to fail, use the `'use strict;'` at the beginning of the function like this:

```javascript
function freezingObject() {
    'use strict';
    const myFrozenObject = Object.freeze({
        myKey: 'This value cannot be changed'
    });

    myFrozenObject.myKey = 'something else'; // error: Uncaught TypeError: Cannot assign to read only property 'myKey' of object '#<Object>'

    // never executed
    console.log('myFrozenObject.myKey = ', myFrozenObject.myKey);
}
```


`Object.freeze` will only freeze the `object` that we pass as an argument. If the `object` contains references to other objects, those can still be modified unless we freeze them as well. For example:

```javascript
function objectFreezeIsShallow() {
    const myFrozenObject = Object.freeze({
        key1: {
            reassignable: 'reassign me'
        },
        key2: Object.freeze({
            notReassignable: 'This value cannot change'
        })
    });

    myFrozenObject.key1.reassignable = 'reassigned';
    myFrozenObject.key2.notReassignable = 'this will not work';

    console.log('myFrozenObject.key1.reassignable =', myFrozenObject.key1.reassignable); // logs: myFrozenObject.key1.reassignable = reassigned
    console.log('myFrozenObject.key2.notReassignable =', myFrozenObject.key2.notReassignable); // logs: myFrozenObject.key2.notReassignable = This value cannot change
}
```

In this case, neither `key1` or `key2` can be reassigned since they’re frozen. The inner object of `key1` however, is not frozen and the `reassignable` key is set to a different value. Note that the `notReassignable` key didn’t have its value reassigned since the object was frozen.

## Recommendations

So, which one(s) should we use and why?

There are lots of reasons to use one over the other, so I divide this into a section per reason. Note that these are my preferences and it’s perfectly fine for other programmers to think differently :)

#### Never use the three of them in the same project

Decide which ones you want to use and stick to those. Using all three of them will be confusing. It will be difficult to know which one to pick and the code will be harder to understand. Be nice to the future readers of your code (including yourself).

#### Prefer consistency

If you’re working on an old project that is using `var`, keep using `var`. This will keep the project consistent.

#### Put variable declarations at the top if using var

If you’re using `var`, place all variable declarations at the top of the function. This will help you see which variables are available in the function and there won’t be any _hoisting_ surprises.

#### Consider older browser support

If your code needs to support older browsers that don’t have `let` or `const`, consider using `var` instead. Alternatively, use a transpiler that converts all `let` and `const` declarations to `var`.

#### Prefer let over var

If you’re starting a new project, I would encourage you to use `let` over `var`. `let` is more predictable, especially for developers coming from a different language.

#### Prefer const over let when possible

I am a firm believer that everything should be a constant except for very special cases in loops or things like that (in those cases, fall-back to `let`). I’ll probably write another post on why using `const` (`final` in Java) is a good choice, but the gist is:

##### Debugging

It will ease debugging. For instance, consider the following code:

 
```javascript
function useConstWhenPossible(myOtherObject) {
    const myObject = myOtherObject.getMyObject();

    // ...
    // 100 lines of code
    // ...

    myObject.doSomething(); // error: Uncaught TypeError: Cannot read property 'doSomething' of undefined

    // ...
    // 100 lines of code
    // ...

    return ...;
}
```

When you see the error, you go to the constant declaration. You can immediately tell that `myOtherObject.getMyObject()` returned `undefined`. There is no need to look through the other 100 lines of code to find if `myObject` ever gets reassigned.

##### Variable naming

You will have to come up with good names for your constants because you won’t be able to reuse them. This will make your code easier to read and more self-documenting.

##### Helps prevent bugs

Another developer (or maybe even you) won’t have a chance to define an existing variable and break the whole function.

#### Use named functions

This will save you a lot of headaches, especially when moving code around. You won’t need to make sure that that your function calls are after the function declarations and everything will work consistently.