DEV Community

Robert Hieger
Robert Hieger

Posted on

var, let and const—from Ambiguity to Specificity

IDE screen with JavaScript code displayed

                                                                    Jorge Jesus, Pexels.com

Introduction

Prior to the advent of the ECMAScript 6 standard (ES6) on June 17, 2015, there was only one way to declare a variable in JavaScript: using the var keyword.

From ES6 onward, standards have been identified with the year of their finalization. For example, ES6 was also known as ES2015. Successive standards have been named in the same way—e.g. ES2016, ES2017, etc.

The var keyword, in earlier standards of JavaScript, dating back as far as the first ratified standard in 1997—ECMAScript 1 (ECMA-262)—served the purpose of declaring variables in the most flexible manner possible, allowing re-declaration of a variable already declared, and reassignment of values regardless of data type. At the foundation of the JavaScript language is that it is a loosely typed language that infers data types dynamically.

As we will explore momentarily, a major update to the JavaScript standard in 2015 provided greater specificity in declaration of variables and some protection against accidental and hard-to-trace errors in which overwriting of values occurred.

Why Is More Than One Declarative Keyword Needed?

Why indeed? This is a very good question. Perhaps the underlying concern was more closely modeling data and the way it is used mathematically. There are values that logically should remain constant, and perhaps partially for this reason, the addition of two new keywords was considered necessary. But there are other reasons as well, and these will be demonstrated later.

In programming, as in mathematics, there is the concept of a constant—that is, an immutable value that is used in calculations and never changes. In languages such as C, C++ and Go, there is a keyword that allows for an immutable value—const.

const can trace its roots back to mathematics, which makes use of constants in several different contexts. Perhaps the best known constant in mathematics is the geometric formula for π:

Formula for PI

                                                     See “Pi.” Wikipedia: The Free Encyclopedia.

This constant is known to be approximately 3.14159.

With the introduction of the ES6 standard, JavaScript added two new keywords to declare variables—let and const. It is fairly self-evident why it might be useful to have the const keyword in JavaScript, as obviously, it would now be possible to declare a variable that is immutable, may not be altered after its declaration and can be used repeatedly in calculations without the concern that its value might be reassigned in the process.

But what about the let keyword? Once again, the let keyword, as is the case for the const keyword, can trace its origin back to mathematics, in this case in formal proofs of mathematical equations.

With the let keyword, it is possible to assign an initial value to a variable. But unlike the value of a variable declared with const, the value assigned using the let keyword may, and in many cases, is reassigned, as in the following example:

let salary = 77_000;

//  Some interceding code...

salary = 85_000;      //  this reassignment of value is allowed.
Enter fullscreen mode Exit fullscreen mode

Why Is the Use of var Now Discouraged?

If you search online references, you will find that the keyword var is neither deprecated, nor obsolete in the current ECMAScript standard. But it is strongly discouraged and considered a poor practice.

Why is this? Primarily, if we consider the multiple millions of lines of code in live applications across the worldwide web, it would have been impractical to eliminate the var keyword without breaking countless instances of legacy code in use.

It is important to understand the inherent weaknesses of the var keyword. Any variable has its scope or where it is accessible to other code in a program file. Variables declared with the var keyword have two different scopes as follows:

Global Scope

A variable that has global scope is one that is declared outside of any function or object. Such variables are visible everywhere within the code. This might sound desirable and a convenience, but it has caused many a headache for developers.

Variables that have global scope can be overwritten by values within a function or outside of a function at will. Below is an example of how a variable in global scope is overwritten within a function:

var amt = 25;

function changeAmt()  {
  amt = 37;

  console.log(amt);      //  Output: 37
}

console.log(amt);        //  Output: 37
Enter fullscreen mode Exit fullscreen mode

The resulting change of value in the above code might be what was wanted. On the other hand, this might just have been a mistake where the programmer did not notice the variable at global scope and accidentally overwrote its value.

You could say, as a result, that there is a potential ambiguity that can lead to unexpected results in calculations.

Function Scope

Function scope begins to resolve some of the ambiguity of variables declared within the global scope—that is, outside of any function or object. But there are still problems that can occur.

A variable declared with the var keyword within a function is accessible only within the scope of that function. It cannot be referenced from the global scope, as shown below:

function printGreeting()  {
  var greeting = 'I live within function scope.';
}

//  Uncaught ReferenceError: greeting is not defined
console.log(greeting);
Enter fullscreen mode Exit fullscreen mode

Of course, the first thing noticeable in the code above is that greeting is declared within the printGreeting() function, but nothing is done with it there. There is no console.log() statement to output the greeting message.

Instead, a console.log() statement occurs outside of the function and greeting is passed to it. But remember that greeting is declared within a function, and any variable declared within a function using the var keyword is inaccessible to anything outside of the function. It is only within the curly braces that this variable can be accessed.

Even with function scope, there still remain a couple of problems with the var keyword.

Another Problem with the var Keyword—It Has No Block Scope

The var keyword does not recognize block scope. With block scope, a value is accessible only within the curly brace delimiters of functions, if statements, for loops, etc. var only recognizes the scope of functions, not these other statements. So you can write code that declares a variable with identical identifier to a global variable within an if statement, and the if statement will alter the value both of its local variable and the matching global variable, as shown below:

var year = 2026;    //  global scope

if (year > 0)  {
  console.log(`Year: ${year}`);
  //  Expected Output: Year: 2026

  year = 1926;    //  reassignment of value

  console.log(`Year: ${year}`);
  //  Expected Output: Year: 1926  
}

console.log(`Year: ${year}`);
//  Expected Output: Year: 1926
Enter fullscreen mode Exit fullscreen mode

In the code above, year is equal to 2026 and it is in the global scope and becomes a property of the global object, which, in browsers is window, but more recently relates to the globalThis object. In Node.js, a globally scoped variable becomes a property of globalThis.

As is shown in the above code, within the global scope, year has a value of 2026. Within the if statement, year is reassigned a value of 1926. It is output within the if statement producing the expected value of 1926. But then, the console.log() statement outside of the if statement also outputs the value of 1926 when we would expect it to output 2026.

Hoisting—the Double-Edged Sword

When a variable is declared using the var keyword, that variable is hoisted (repositioned in memory) to the top of its scope, whether that scope is function scope or global scope.

Its value is set to undefined even though it might have been initialized with values in the declaration.

From one point of view, hoisting could be viewed as a flexible way of handling variables. You don’t have to worry about where you declare the variable, because it will be hoisted to the top of its scope.

Interestingly, functions are hoisted to the top of their scope and any variables contained within them are also hoisted and initialized. This way, technically it does not matter where you define a function, because it is hoisted, along with any values contained within it, to the top of its scope.

So code such as in this example…

printGreeting();

//  Some interceding code...

function printGreeting()  {
  var greeting = 'Hi!';
  console.log(greeting);
}
Enter fullscreen mode Exit fullscreen mode

…is completely admissible.

However, it is considered poor form to call a function before it has been declared. Code like this is more difficult to maintain as the function might be lost in a sea of code. It is generally better practice to position all needed functions prior to the position in the code that they are called.

Enter the let and const Keywords

Up to this point, we have seen and explored the behavior of the var keyword. We’ve demonstrated both its flexibility and the considerable pitfalls its use introduces.

When the ECMAScript 2015 standard was finalized, the let and const keywords were added. They work by a more clearly defined set of rules than their ancestor, the var keyword. One very important concept we will be exploring is both the let and const keywords’ use of block scope, of which we learned earlier, but will now explore in greater depth.

The let Keyword

The let keyword allows the creation of local variables that are block-scoped. This is significant, because although a variable declared using let can be defined within the global scope, it does not create a property on the global object—window or globalThis (“let,” Mozilla Developer Network). To illustrate this point, let’s take a look at the crucial differences between variables declared using the var keyword and those declared using let. First, let’s consider how var interacts with the global object:


Screenshot of JavaScript console with declaration of string variable


In the above screenshot from Google Chrome’s JavaScript console, the variable myName is declared and initialized using the var keyword with the value of 'John Smith’. Now let’s take a look at how this declaration affects the globalThis object that defines the global scope:


Screenshot of JavaScript console showing addition of myName property to globalThis


In the above screenshot, we see a partial output of the globalThis object in Google Chrome’s JavaScript console. It contains many of the properties one would expect to be associated with the global object. But at the bottom, you see the property myName with a value of “John Smith”.

Now let’s try the same thing using the let keyword:


Screenshot of JavaScript console showing declaration of string variable using let keyword


In the above screenshot, the same variable (this time with a value of 'Jane Smith') is declared using the let keyword. Now let’s take a look at the effect this has on globalThis:


Screenshot of JavaScript console showing globalThis with no additional properties appended


Now we see an immediate difference. In the screenshot above, which shows the same partial output of globalThis as in our earlier example using var, the last line contains no property called myName. This is because the let keyword does not set any properties on the global object when a variable is declared.

Block Scope in Greater Depth

We have already seen that block scope limits the reachability of a variable so that it is accessible only within the curly brace delimiters in which it is declared. But there are several groupings that use the curly braces.

Among the most frequently used would be the following:

  • The switch statement

  • The try...catch statement

  • Any of the for loop variants

  • The body of a function as shown below:

function listFruits()  {

  //  fruits is only reachable within this function
  let fruits = ['apple', 'banana', 'grapes', 'orange', 'mango'];

  fruits.forEach( fruit => {
    console.log(fruit);
  });

}

listFruits();

//  Expected Output:
//  apple
//  banana
//  grapes
//  orange
//  mango
Enter fullscreen mode Exit fullscreen mode

Differences Between var and let

Feature var let
Scope var declarations are scoped either to function or global scope. let declarations are scoped to block and function scope as well because function bodies are blocks.
Hoisting var declarations are hoisted to the top of their scope and assigned an initial value of undefined. let declarations are hoisted but only the variable name (identifier) is hoisted. There is no default value as the initialization has not yet taken place. This is because of the temporal dead zone (TDZ).
Adds Properties to globalThis The variable becomes a property of globalThis. Initially its value is undefined prior to its initialization. No property is added to globalThis.
Can be redeclared Yes No
Can be used as the body of a code block Yes No. Will throw error—Uncaught SyntaxError: Lexical declaration cannot appear in a single statement context.

You might have noticed the perhaps unfamiliar term temporal dead zone in the table row for Hoisting. What exactly is this? In short, the temporal dead zone is that portion of code that occurs “from the start of the block until code execution reaches the place where the variable is declared and initialized” (“let,” Mozilla Developer Network).

The const Keyword

Earlier we spoke of constants and the const keyword, and the fact that when it is used in a declaration, the variable is immutable and thus, its value cannot be reassigned. To demonstrate this point, let’s look at the code listing below:

const isLoggedIn = true;

const logout = function()  {
  isLoggedIn = false;    //  This will throw an error
  console.log(isLoggedIn);
};

logout();

//  Expected Output:
//  Uncaught TypeError: Assignment to constant variable
Enter fullscreen mode Exit fullscreen mode

This is not the entire story of what the const keyword does, however, or how it differs from let.

Another twist that sometimes leads to confusion is that when const is used in a declaration where an object is assigned as its value, properties within the object may be updated, deleted or even added to that object (Mozilla Developer Network).

const is scoped in the same way as let. Like let, it is scoped to block and function scope, because as shown in the table above on the differences between var and let, the body of a function is a block.

Other Things to Know about const

First, it should be clarified that in many ways const and let behave in very similar ways, but their purposes are different from one another, nonetheless.

One important similarity is that the temporal dead zone applies to variables declared with the const keyword, just as it does for let declarations.

Another notable similarity is that declarations using the const keyword also do not create any new properties on the globalThis object, as is the case with let.

Yet another similarity between the two declarative keywords is that a const declaration also may not be the sole statement within the body of a code block.

Given all of these similarities, where does const truly differ from let? Where it truly differs is that variables declared with const may not be reassigned as in the following code:

const userName = 'Bobby';

//  Throws Uncaught TypeError: assignment to constant variable.
userName = 'Clara';
Enter fullscreen mode Exit fullscreen mode

Another difference is that const declarations must initialize a value, which certainly makes intuitive sense, as a constant is supposed to be immutable. You would not use undefined as its initialization, as this is a value assigned by JavaScript itself. You could initialize its value to null, which represents the intentional absence of a value, and so it is an initialization. However, the code below is not admissible:

const userName;    //  Invalid declaration

//  The above declaration will throw an error:
//  Uncaught SyntaxError: Missing initializer in const declaration
Enter fullscreen mode Exit fullscreen mode

Subtleties of the const Keyword

There are some subtleties we touched on earlier with regard to reassignment of values in const declarations. These subtleties are often the cause of confusion. Consider the following examples:

Reassignment, Insertion or Deletion within an Object

Consider the following object:

const employee = {
  lastName: 'Doe',
  firstName: 'Jane',
  skills: [
    'HTML',
    'CSS',
    'JavaScript',
    'TypeScript',
    'Node.js',
    'Angular'
  ],
  yearsExperience: 10
};
Enter fullscreen mode Exit fullscreen mode

The screenshot below shows the reassignment of a value in an object within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing value reassignment within an object                                                               Value Reassignment

Three values within the object employee have been successfully reassigned, changing the properties of lastName, firstName and yearsExperience.

The screenshot below shows the insertion of a value in an object within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing value insertion within an object                                                                   Value Insertion

Here is a successful insertion, using the Array.push() method, of a member within the skills array property of employee, adding the value of ‘Python’.

The screenshot below shows the deletionof a value in an object within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing value deletion within an object                                                                 Value Deletion

Here is a successful deletion, using the Array.pop() method, of the final member within the skills array property of employee.

Reassignment, Insertion or Deletion within an Array

Now that we have seen reassignment, insertion and deletion of values within an object, let's observe these same operations within an array:


const books = [
  { title: 'Red Mars', author: 'Kim Stanley Robinson' },
  { title: 'Green Mars', author: 'Kim Stanley Robinson' },
  { title: 'Blue Mars', author: 'Kim Stanley Robinson' }
];
Enter fullscreen mode Exit fullscreen mode

The screenshot below shows the reassignment of a value in an array within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing reassignment of a value within an array                                                             Value Reassignment

In this example, we have an array comprised of three objects containing book titles and their authors. Here we see successful reassignment of the title property of the third member element in the books array.

The screenshot below shows the insertion of a value in an array within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing insertion of two values within an array                                                                   Value Insertion

Here is the successful insertion, using the Array.push() method, of two member objects to the books array.

The screenshot below shows the deletion of a value in an array within the Google Chrome JavaScript console:

Screenshot of Google Chrome JavaScript console showing deletion of a value within an array                                                                 Value Deletion

Finally, here is the successful deletion, using the Array.pop() method, of the final object element in the books array. The array is now reduced to 4 elements, as it was before.

Herein lies the point of confusion for some. If const declarations are used to create immutable variables that may not be changed once they are declared, then how is it possible to create the mutations illustrated above, both within an object and also within an array?

First, ask yourself the question “What is an object and what is an array?” In fact both are objects, with the array being a specialized type of object with some pre-defined properties and behaviors.

When an object or array is declared using const, the JavaScript engine allocates a memory address to the identifier (variable name). The contents stored within the variable at that address may change, but the name may not, nor may it be redeclared.

Notice that JavaScript allocates a memory address to the identifier, but not to the values referenced by the identifier. As stated by the Mozilla Developer Network:

The const declaration creates an immutable reference to a value. It does not mean the value it holds is immutable—just that the variable identifier cannot be reassigned. For instance, in the case where the content is an object, this means the object's contents (e.g., its properties) can be altered. You should understand const declarations as ‘create a variable whose identity remains constant,’ not ‘whose value remains constant’—or, ‘create immutable bindings,’ not ‘immutable values’ (“const”).

Thus, it is quite possible to change the value of properties, add properties or delete properties from an object. As an array is a specialized object type, you can alter (reassign), add or delete elements from it even if it is a constant.

To further clarify this point, let’s take yet another look at what block scope is and what it really does. Earlier, it was stated that block scope confines the reachability of a variable to within the delimiters of curly braces. This much is true, but there’s still more to it. Consider the following code:


const MONTH = 'May';  //  this is a global constant
const DAY = 31;

if (DAY === 31)  {
  const MONTH = 'June';  //  this MONTH constant is in
                         //  another code block, thus it
                         //  has another memory address
}
Enter fullscreen mode Exit fullscreen mode

In the first line of the above code the constant MONTH is declared and initialized to the value of ‘May’. Another constant, DAY is declared and initialized to the value 31. Finally, in an if statement, because DAY === 31 evaluates to true, a constant of MONTH is declared and initialized to the value of ‘June’.

How is this possible? Shouldn’t redeclaring MONTH throw a syntax error such as this:

Uncaught SyntaxError: redeclaration of const MONTH
Enter fullscreen mode Exit fullscreen mode

In this case, you would not get the above error because both instances of the constant MONTH occur in different blocks. The first one occurs within global scope, and the second occurs in an if block. Thus they are two different constants.

An Important Caveat

Should you test out the following code in the JavaScript console of major browsers, you will get varied results:


const MONTH = 'May';

//  Some interceding code...

const MONTH = 'June';  //  this should throw a syntax error
Enter fullscreen mode Exit fullscreen mode

With some browsers, you will get the expected error message in the console, as with Mozilla Firefox:

Screenshot of Mozilla Firefox JavaScript console throwing a SyntaxError for redclaration of a const                                      Error thrown in Mozilla Firefox JavaScript Console

Or in Apple’s Safari browser, you get this error message:

Screenshot of Safari JavaScript console throwing a SyntaxError for redeclaration of a const                                           Error thrown in Apple Safari JavaScript Console

However, some browsers will display no error message in the console for this code. For example, Google Chrome displays no error message. This is not in compliance with the ECMAScript standard, but because each vendor designs their developer tools outside of that standard, they might deviate from throwing the error in the JavaScript console. This might be done as a convenience to developers so that they do not have to reset the console and start entry of test code from scratch. But rest assured. Redeclaration of a constant within the same scope is not allowed in JavaScript.

To get a complete picture, let's review the differences between var, let, and const.

A Final Look at the Three Declaration Keywords

Feature var let const
Scope var declarations are scoped either to function or global scope. let declarations are scoped to block and function scope. const declarations are scoped to block scope. As functions are blocks, they are also scoped to function scope as well.
Hoisting var declarations are hoisted to the top of their scope and assigned an initial value of undefined. let declarations are hoisted but only the variable name (identifier) is hoisted. There is no default value as the initialization has not yet taken place. This is because of the temporal dead zone (TDZ). As is true for var and let, const declarations are hoisted, but only their identifiers are hoisted. There is no initial value.
Adds Properties to globalThis Yes No No
Can Be Redeclared Yes May not be redeclared within the same scope. May not be redeclared within the same scope.
Can Be Used as the Body of a Code Block Yes No No

Conclusion

We can see from the foregoing that var remains as a legacy keyword of JavaScript and cannot be eliminated from the language due to the millions of lines of code in which it is used on the world wide web. Best practice dictates that it should not be used in new code bases due to its ambiguous and error-prone nature.

let and const have provided a degree of safety for data integrity and created a far less ambiguous means of declaring and initializing variables, functions and objects.


                                                                 Works Cited

“Pi.” Wikipedia: The Free Encyclopedia, Wikimedia Foundation, 8 March 2026, https://en.wikipedia.org/wiki/Pi. Accessed 8 March 2026.

“let.” Mozilla Developer Network (MDN), Mozilla Foundation, 8 Jul 2025, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let. Accessed 14 March 2026.

Ibid.

“const.” Mozilla Developer Network (MDN), Mozilla Foundation, 8 Jul 2025, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const. Accessed 17 Mar 2026.

Ibid.

Ibid.

Top comments (0)