JavaScript provides three methods for declaring variables. We can use the var, let, and const keywords. This abundance of choices is a source of confusion for a beginner developer.
At least it was for me.
While taking my first steps in the world of JavaScript. I wondered, “Why should a programming language use so many ways to make the same thing?”.
The fact is that this didn’t happen by design but because of the ever-evolving nature of JavaScript.
Each year in JavaScript, new things are added or old things are being depreciated.
From the beginning, and until the rise of ES6, the only way to define a variable was by using the var keyword. The const and let keywords were introduced later, with ES6, as a means to overcome the problems caused by the use of the var keyword.
We are going to address these problems and see how const and let made a difference, and also which to use and when in the following article.
JavaScript Hoisting
Before we move on to the variable declarations, we should talk about the concept of hoisting in JavaScript. Hoisting refers to the process of moving a variable to the top of its function, block, or script source.
Below is an example of how hoisting can accidentally make us declare a global variable:

Here, the variable i is hoisted at the top of the script source, all the way up to the Window object.
The strange thing is that i in the example is not declared but implied.
In JavaScript, any variable we don’t declare becomes a property of the global (‘Window’ in our environment) object.
For example:

So, the previous example is similar to:

JavaScript, under the hood, pushes the variable i up to the top (hoisting), and declares it right there as a property of the Window object.
We see that it is very easy to declare a global variable accidentally. Our intention for i was for it to be a local variable, but by forgetting to properly declare it, we now have a mess.
The var variable
How hoisting affects var
A var variable declared anywhere in the code is hoisted at the top. This provides, as a result, the flexibility for a var variable to be accessed BEFORE it’s declared.

Here, the variable sum_var is subject to hoisting, thus accessing it in line 1 logs “undefined” without producing an error.
Once declared, the variable has been moved to the top of the scope, and when the console object logs the variable’s value, it prints “undefined” since every var variable at the time of declaration evaluates to “undefined”.
The problems caused by the use of var have mainly to do with the concept of scope.
Let’s see an example of a for loop.

We see that i is mutating outside of the for loop scope. It had a value of 5, the last time it was called in the loop, and outside the loop has the value of 6.
In programming languages like PHP and Java, that wouldn’t be possible because the i variable could only be scoped to the context of the loop and not outside of it.
But in the pre-ES6 JavaScript era, there was only one type of variable scope, and that was function scoped and not block scoped.
For example:

Variables we declared with the var keyword are function scoped.
Namespace pollution in var
Another characteristic of the var variable is the possibility of duplicate variable declarations. Even in strict mode, var permits duplicate variable declarations without throwing an error.
For example:

The code, whice should be in distinct namespaces, is now part of a shared namespaces.
Let’s see what happens in the same example if we fail to properly declare the variable i in the loop:

Here, the var i is reassigned in line 5 as i=1. The two variables inside and outside of the for block are considered one since the implied i is hoisted on the global scope.
This issue is less likely to occur in a small application.
But since introducing a variable with the same name does not throw an error, fixing bugs in a large-scale application becomes a chore.
To remedy these issues, caused by the use of the var statement, the ECMA organization introduced the const and let statements in ES6 version of JavaScript.
The const variable
When we use the const keyword to declare a variable within a block, its scope will only be within that specific context.
Declare a variable within a block:

Declare a variable within an If statement:

Contrary to var variables, const variables are not function scoped:

In the example, the message variable is only accessible inside the if...else block. When we try to log the variable inside the function’s scope, we get an error.
const variables must be initialized upon time of declaration:

In the case of a const variable, the value ‘undefined’ is not automatically assigned upon declaration, thus we have to provide our own value immediately.

A const variable is not hoisted at the top, so it can’t be accessed BEFORE its declaration:

Once we declare a const variable, we cannot reassign it to point to a different value.

const and object mutability
Objects in JavaScript are passed by reference. This means when we assign an object to a variable, we’re not pointing to the object itself, but rather to the memory location of the object.
Mutating object properties
Even though we can’t reassign the const variable to a new object, we can still modify the properties within the original object. This is because we’re still referencing the same object in memory.

But we can mutate properties:

The let variable
let variables, just like var variables, can be defined without being initialized. They are assigned by the engine the value ‘undefined’ when declared.

let variables are mutable.

We see in the example that changing a primitive’s value is acceptable.
Other than that, the let variable is identical to the const variable.
Which one to use?
The choice should obviously exclude the var statement, and be made between let and const.
The popular approach, which I adhere to, is to use the const keyword for all the variables that we don’t plan to reassign at some point in the future. For these, we use the let keyword.
We do this because we want to avoid a mutable state, at least when there is no reason for it to exist. Immutable code is safe code.
For example:

Here, we calculate a rectangle’s width. There is no reason for that width to change later.

But here we need the variable to be mutable. We keep score and will continually change throughout the duration of the game.
Another example of having a reason to declare a mutable variable is in a for loop:

The let i = 1 statement initializes the i variable with the value of 1. Then, during each iteration of the loop, the i++ expression increments the value of i by 1.
This is an example of variable assignment. The let variable allows us to do that. We couldn’t achieve that with a const variable though.

The declaration is only evaluated once before the loop body is executed, so we get 1. The reason we’re getting an error is that we’re trying to reassign a value to a constant variable i inside the loop, which is not allowed.
In JavaScript, variables declared with const cannot be reassigned after they are initialized. In our loop, we are trying to increment i with i++, which is a reassignment and not allowed for const variables.
We could use let instead.
NOTE: we can use const with for...of loops
With a for…of loop, we can use const instead of let because the variable here is not being reassigned on each iteration of the loop. Instead, it is being declared as new variable in each iteration and assigned the value of the current element in the array.
The const keyword used to declare the variable cat (line 3), creates a new block-scoped variable in each iteration of the loop. This means that the variable cat only exists within the scope of the loop iteration and is not accessible outside of that scope.