Declaring JS Variables – var, let & const

Note: This post is work-in-progress learning-note and still in active development and updated regularly.

JavaScript (JS) variables are an important concepts. JS variables are used as containers to store different data types. To understand and use JS variables, we must of be familiar with variable scope and variable hoisting. Lets look at some of the basic features of variables declaration using var, let & const.

Variable Declaration

MDN describes declaring variables with the following three ways:

  • With the keyword var (eg.  var x = 42). This syntax can be used to declare both local and global variables.
  • By simply assigning it a value (eg. x = 42). If this form is used outside of a function, it declares a global variable. It generates a strict JS warning. You shouldn’t use this variant.
  • With the keyword let (eg. let y = 13). This syntax can be used to declare a block-scope local variable.
1. Variable Names

All variables must have have unique names called identifiers. The general rules for constructing unique variable identifiers :

  • Names can contain letters, digits, underscores, and dollar signs.
  • Names must begin with a letter
  • Names can also begin with $ and _ (but we will not use it in this tutorial)
  • Names are case sensitive (y and Y are different variables)
  • Reserved words (like JavaScript keywords) cannot be used as names
2. Variable Assignment

JS variables can hold different data types, including numbers, string, object, Boolean and null as shown in our example below:

//Variables holding different data type
var car = "Toyota";  // strings
var year = 2018; //numbers
var model = [ "Corolla", "Camry", "RAV4" ]; // arrays
var color = { corolla: "red", camry: "blue" }; //objects
var success = true; //Boolean
var nothing = null; // nothing or null

//invoking value
console.log(car);

//OUTPUT
Toyota
3. Terminology

It is essential to understand the var definition terminology which helps to better understand & untangle variable scope & hoisting. Dmitri Pavlutin explains this process in his post – JavaScript variables lifecycle: why let is not hoisted.

  1. Declaration: Variable is registered with a name it is called declaration or creation.
  2. Initialization: When a variable is declared it gets initialized automatically (JS engine assigns memory allocation)
  3. Assignment: When a value is assigned to a variable.

Declaration with keyword: var

Before ECMAScript 2015 (ES6), JS variables were declared using only var keyword.  Use of var keyword to declare variables is encountered in almost all older codes and many tutorials & learning resources. Therefore, it is important to understand declaration using var keyword.

Using var keyword, a variable can be declared as shown in the following example:

//declare function
function greeting() {
  var sayHi = "Hello, World!"; //  a local variable (1)

  console.log(sayHi); 
}
//Invoke function (2)
greeting(); // Output => Hello, World!

// Output value of 'sayHi' (3)
console.log(sayHi); // out put => Error, sayHi is not defined

In the example above, var sayHi is local scoped. It’s value is available within the greeting() function only (2) – function scoped. It’s value is not available outside of function (3) greeting() and throws an error.

Variables declared with var keyword have NO block scope. They are either function-wide or global, they are visible through blocks.

function sayHi() { 
 if (true) { 
   var phrase = "Hello"; 
 } 
console.log(phrase); // works 
} 
//invoke function
sayHi(); // “Hello” (1)

//print value
console.log(phrase); //(2)
//output
ReferenceError: phrase is not defined

In the example above, var keyword is NOT limited to bock-scope i.e. within curly braces {} but to the entire function (1). However, it is not accessible outside the function scope (2).

1. Variable Hoisting

Variable declaration is affected by variable hoisting. In JS variables can be accessed before they are declared anywhere in the code. This process known as hoisting occurs when JS engine processes variable declaration to the top of the function or global code. Lets examine this behavior using the following MDN code example:

//initialize function
function myFunction() {
  console.log(num); // undefined
  var num = 111;
  console.log(num); // 111
}

//JS engine processed above code as follows
function myFunction() {
  var num; // hoisted 
  console.log(num); // undefined
  num = 111;
  console.log(num); // 111
}

In the example above, var declaration was hoisted (moved to top (line 10) above console.log(num) line 11) but not initialized therefore it log undefined value. The var num was defined or assigned a value ( = ) at line 12. When it was log (line 12) after value assignment, it output an expected value.

Tip: Variable declaration are only hoisted but variable assignment are not. It occurs at the place when it occurs in the  code.

2. Declared vs Non-declared Variables

While using var keyword to declare variables, it is essential to understand the difference between Undefined and Undeclared. In a very simple language,

  • Undefined refers to variable is declared but its value has not been assigned.
  • Undeclared refers that variable has not been declared yet.

The differences between Declared vs Undeclared variable is explained in MDN as follows:

2.1. Declared variables are available where they are declared. Undeclared variables are always global.

// Initialize function x()
function x() {
  y = 1;   // Throws a ReferenceError in strict mode
  var z = 2;
}
//invoke function
x();
//OUTPUT
console.log(y); // logs "1" 
console.log(z); // Throws a ReferenceError: z is not defined outside x

In the example above, variable y is only assigned a value of 1 (line 3) in function x() but not declared. Therefore, y behaves as global variable could be accessed outside of its functional scope (line 9). On the other hand, variable z is declared & defined (line 4) in function x() thus it’s scope is restricted to function x(). When referenced (line 10) from outside of function x(), it throws ReferenceError.

2.2. Declared variables are created before any code is executed. Undeclared variables do not exist until the code assigning to them is executed.

Lets examine this feature in the following example from the MDN. The variable a is called (1) without initialization and it throws ReferenceError because it does not exist yet. It stops executing next logout cod because the previous non-assigned code was not executed because of an error

//log a undefined variable
console.log(a);         // (1)      
console.log('still going...')  // (2)
//OUTPUT
ReferenceError: a is not defined // (1)
//never executed (2)

However, in the following examples variable a was declared (line 2) and logged out its value only after its initialization (line 3) it outputs a undefined value (line 6) and it executes next line of code as well (line 4) and outputs expected string (line 7) still going ... .

//Initialize var a 
var a;
console.log(a);     // (1)           
console.log('still going...');  // (2)
//OUTPUT
undefined  // (1)
still going ... // (2)

2. 3. Declared variables are a non-configurable property of their execution context (function or global). Undeclared variables are configurable (e.g. can be deleted).

Lets examine this feature in the following example from the MDN.The variable a is declared and defined (line 2) where as var b is assigned value of 2 but not declared yet. Both variables a and b logout expected output (lines 5-6).

//initialize variables a, b
var a = 1;
b = 2;
//OUTPUT
console.log(a);  => 1
console.log(b); => 2

//Lets make some changes
delete this.a; 
delete this.b; //delete & doesn't exist
//OUPUT
console.log(a); => 1
console.log(b); => ReferenceError: b is not defined

Now, lets make some modification and delete both these variables issueing delete this.a; and delete this.b; statement (lines 9-10). When checked their values by loging out (lines 12-13), we still get expected value variable a (line 12) but it throws RreferenceError for value of b, meaning the undeclared var b was successfully deleted.

MDN Recommendation: it is recommended to always declare variables, regardless of whether they are in a function or global scope. In strict mode, assigning to an undeclared variable throws an error. All var declaration should be at the top of the function, to avoid variable ‘hoisting.

When to Use var

Because of the above discussed scope & hoisting features associated with var keyword, it is recommended to avoid use of var in variable declaration. It creates lot of confusion and use of let & const (discussed below) do exactly same thing but are more cleaner & less confusing for variable declaration.

Declaration with Keyword: let

Variables can be declared using new keyword let which is almost the same as var keyword. In most newer script we find use of let to declare variables instead of var. The new keyword let allows us to declare variables with their scope limited to block level, statement, or expression on which they are used.

1. Assignment with let

Similar to var keyword, variables can be declared using let keyword to assign different data types, including numbers, string, object, Boolean and null. Using our example from  above:

//Variables holding different data type
let car = "Toyota";  // strings
let year = 2018; //numbers
let model = [ "Corolla", "Camry", "RAV4" ]; // arrays
let color = { corolla: "red", camry: "blue" }; //objects
let success = true; //Boolean
let nothing = null; // nothing or null

//invoking value
console.log(car);

//OUTPUT
Toyota
2. Hoisting Rules with let

As also described in previous section, variable declaration is affected by hoisting. To quote from MDN let scope rules: “variables declared by let have their scope in the block for which they are defined, as well as in any contained sub-blocks. In this way, let works very much like var.”  The main difference is that the scope of a var variable is the entire enclosing function.

Lets examine the differences in hoisting rules between var & let in the following examples:

Example 1: The following example & its explanation is borrowed from Tania Rascia‘s post Understanding Variables, Scope, and Hoisting in JavaScript.

var letTest = true;

// Initialize a global variable
let model = "Corolla";

if (letTest) {
  // Initialize a block-scoped variable
  let model = "Camrey"; // local-scoped 
  console.log(`It is VarTest. My vehicle model is ${model}.`); // (1)
}

console.log(`It is varTest. My car model is ${model}.`); // (2)

//OUTPUT
It is varTest. My vehicle model is Camrey. // (1)
It is varTest. My vehicle model is Corolla. // (2)

In the example above, variable model has a global-scope value of 'Corolla' but the model variable was assigned another value 'Camrey' inside the curly braces as a block-scoped value, which available inside the block only (1). When we log the value of model from outside block, it outputs global-scoped value (2).

If we look at the same example above using var keyword (instead of let) we get different output result.

var varTest = true;

// Initialize a global variable
var model = "Corolla";

if (varTest) {
  // Initialize a block-scoped variable
  var model = "Camrey";
  console.log(`It is VarTest. My vehicle model is ${model}.`); // (1)
}

console.log(`It is varTest. My car model is ${model}.`); // (2)

//OUTPUT
It is varTest. My vehicle model is Camrey.  // (1)
It is varTest. My vehicle model is Camrey.  // (2)

Unlike in the previous example, the value of model variable is same both inside and outside of the block. In this case the value of model was re-assigned to a new value 'Camrey' because var does not recognize if or function as different scope.

Example 2: Lets examine another example from MDN let scoping rules to understand the difference between var & let in function-scope context:

//Initialize a function
function letTest() {
  // initialize a global-scope variable
  let model = 'corolla'; // (1)
  if (model) {
    // re-assign a new value in block-scope
    let model = 'Camrey';  // different variable (2)
    console.log(model);  // Camrey (2)
  }
  console.log(model);  // corolla  (1)
}
//invoke function
letTest();

//OUTPUT
Camrey // local value (2)
Corolla  // global value (1)

In the example above, model variable was initialized with let keyword a function-scope global value of 'Corolla' (accessible within the letTest function only). Now the same variable was reassigned with a value of 'Camrey' which is accessible only inside the if block scope (2). let defined value within curly braces are block-scoped. The value of model variable outside the ifblock is its function-scope global value ‘Corolla‘ (1).

If we examine the same function using var to initialize model variable its value will be different from that of initialized with let keyword.

// Initialize a function
function varTest() {
// initialize a global-scope variable 
var model = 'corolla';
  if (model) {
   // re-assign a new value in block-scope
    var model = 'Camrey';  // same variable (1)
    console.log(model);  // Camrey
  }
  console.log(model);  // Camrey  (2)
}
//invoke function
varTest();

//OUTPUT
Camrey  // reassign value // (1)
Camrey  // reassign value  (2)
3. Temporal Dead Zone Error with let

Variables declared with let keyword are not hoisted, meaning the let declaration don’t move to the top before execution. Unlike the variable declared with var keyword, referencing let variable in the block results in ReferenceError. The variable is in Temporal Dead zone from the start of the block to the variable initialization is processed.

More detailed explanation of Temporal Dead Zone is described in separate post Variable Declaration with let & Temporal Dead zone.

Declaration with Keyword: const

Just like var and let, variables can also be defined using const (constant) keyword. Unlike var, const must be initialized in the declaration statement.

1. Syntax and Some Key Features

1.1. Syntax rules for naming const are similar to var & let (see above). They can be declared in lower or uppercase. But as a general convention cons are written in all upperCase with underscore. Following example from MDN:

//Example from The Modern JS Tutorial
const COLOR_RED = "#F00";
const COLOR_ORANGE = "#FF7F00";

// ...when we need to pick a color
let color = COLOR_ORANGE;
console.log(color); // #FF7F00

In the example above, we assigned COLOR_RED and COLOR_ORANGE with their corresponding hex values (fixed) and the constant variables can be used wherever needed. This practice is commonly used is writing CSS styles.

1.2. Values of const can’t be changed or re-assigned.

//declare and assign a value
const MY_NUM = 10;
console.log(MY_NUM);  //Output => 10

//re-assign value to MY_NUM const
MY_NUM = 20:
console.log(MY_NUM); //=> TypeError: Assignment to constant variable.
console.log(MY_NUM); //Output =>10   (value not assigned)

In the example above, MY_NUM was assigned a value of 10 (line 2) and we get expected output (line 3). But when reassign a new value to MY_NUM (line 6), it throws errors (lines 7-8).

1.3. const can’t be re-declared with the same name with a function (1) or a variable (2) in the same scope.

// This will cause error (1)
function myFunction() {};
const myFunction = 5;
 
//OUTPUT
Uncaught SyntaxError: Identifier 'myFunction' has already been declared

// This will cause error (2)
function myFunction() {
  const car = 5;
  var car;

  //statements
}
//Invoke Function myFunction
myFunction():

//OUTPUT
Uncaught SyntaxError: Identifier 'car' has already been declared

In the example above, we declared myFunction() as function (line 2). When we re-declared myFunction as const (line 3), JS engine throws SyntaxError (line 6). Likewise in case (2) when we re-declared const car with var (line 11) within myFunction() functional scope lines (8-12), SyntaxError error was thrown (line 19)

1.4. Assigning to a const variable is a syntax error

//attempt re-declare cons with var
const MY_NUM = 10;
var MY_NUM; 

//OUTPUT
TypeError: Assignment to constant variable.

In the example above, MY_NUM was declared & assigned a value (line 2) using const keyword. When we reassigned the same MY_NUM variable using var, it throws TypeError (line 6).

1.5. Constant must be initialized or else it throws syntax error.

//constant declared but not initialized
const MY_NUM;

//OUTPUT
SyntaxError: Missing initializer in const declaration

In the example above, MY_NUM variable is declared using const keyword (line 2) but was NOT initialized. While declaring variables with const, it MUST be be initialized otherwise it throws SyntaxError (line 5).

1.6. JS object can be assigned to constant and object attributes are not protected.

// const also works on objects
const MY_OBJECT = {"key": "value"};
// Overwriting the object fails
MY_OBJECT = {"OTHER_KEY": "value"};

//OUTPUT
TypeError: Assignment to constant variable.

//object attributes are not protected
MY_OBJECT.key = "otherValue";

//OUTPUT
"otherValue"

In the example above, MY_OBJECT variable was declared with const and name:value pair (line 2). In line 3, we re-assigned the same MY_OBJECT with another key, it throws TypeError (line 7). However, when we used object attribute MY_OBJECT.key (using object name with dot notation) in line 10, we can safely re-assign it with a new value (line 10) without any error (line 13).

2. const Scope Rules
  • const are block-scoped (similar let) and can be defined as global or local  in the context to the block in which it is declared.
  • If const keyword is omitted, it is assumed as var.

See above under let section. and example  hoisting and const from Dmitri’s blog post

Differences between var, let and const

From the previous sections, we learned that variables can be declared using var, let and const keywords. We also learned from variable declaration that it is context-based (block or function) and scope- & hoisting-rules make it even more confusing.

Tania Rascia differentiates declaration with var, let and const in the following table based on scope, hoisting and reassignment.

Keyword Scope Hoisting Can Be Reassigned Can Be Redeclared
var Function scope Yes Yes Yes
let Block scope No Yes No
const Block scope No No No

As a general convention, it is recommended to declare variables using const and let. Wherever there is need to reassign a variable or conditional statements, use of let keyword is recommended. When working with new projects use of var can be avoided completely but understanding of var keyword and its use is very important to understand legacy codes.

Resources & Further Readings:

While preparing this post, I have referred the following references extensively. Please refer to the original posts for more detailed information.