DEV Community

Cover image for JavaScript Fundamentals - Variables: The Foundation of Any Program
Martín Aguirre
Martín Aguirre

Posted on

JavaScript Fundamentals - Variables: The Foundation of Any Program

Introduction

As a fundamental concept in every programming language, variables are the foundation of any program. They let us store and manipulate data that we will use later in our program. As a useful resource, variables are abstract storage locations, identified by an associated name, which contains a value.

Declaring Variables: const, let and var

In order to create a variable in JavaScript, we must declare it, which is the proper term for this action. JavaScript gives us three keywords to declare variables: var, let, and const.

Declaration of a Variable with var

The var statement is used to declare a function-scoped, or globally-scoped variables, optionally with an initialized value.

Main characteristics of var:

  • Introduced when JavaScript was first created in 1995.
  • Has function scope (not block scope).
  • Can be redeclared in the same scope without error.
  • Hoisted to the top of its scope and initialized with undefined.

Simple example:

var car = "Mercedes";

var car = "Volkswagen"; // redeclaration works
console.log(car) // "Volkswagen" gets printed
Enter fullscreen mode Exit fullscreen mode

Nevertheless, this sort of generous flexibility may lead to bugs or glitches in larger programs or systems. The use of this approach is not advised, unless you have a very good reason to do so.

Declaration of a Variable with let

The letstatement is used to declare a re-assignable, block-scoped local variable, optionally with an initialized value.

Main characteristics of let:

  • Introduced in ES6 (2015).
  • Has block scope (safer than var).
  • Cannot be redeclared in the same scope.
  • Can be reassigned.

A simple example of how to declare a variable with no value, or at least not yet:

let varName;
Enter fullscreen mode Exit fullscreen mode

In a technical sense, this varName variable is undefined, so we'll assign it a value:

varName = "House";
Enter fullscreen mode Exit fullscreen mode

Since it has already been declared, we do not use the let keyword again; we just want to assign a value to it, so we write the variable's name, add the assignment operator (=), and assign the "House" value which is of type string in this case.

Nonetheless, this is somewhat unpractical. A more straightforward way of completing the job would be as follows:

let varName = "House";
Enter fullscreen mode Exit fullscreen mode

Use of the let keyword, variable name or "identifier", and the value that needs to be assigned.

Being more clear and concise:

let color = "blue";

// let color = "red"; Error: It's already declared

color = "red"; // reassignment works
console.log(color); // "red" gets printed
Enter fullscreen mode Exit fullscreen mode

Additional examples of variables with the let keyword:

let myDog = "Pipo";
console.log(myDog) // "Pipo" gets printed

let streetNum = 477
console.log(streetNum) // 477 gets printed

let isFalse = false
console.log(isFalse) // false gets printed
Enter fullscreen mode Exit fullscreen mode

As you may notice, there's a standard way of writing the names we assign to our variables. In JavaScript, this method is known as camelCase. This typographical convention is a way of writing phrases without spaces, where the first letter of each word is capitalized, except for the first letter of the entire compound. We benefit from it by making things like variables and functions easier to read

Declaration of a Variable with const

The const statement is used to declare a block-scoped local variable. The value of a constant must be assigned when initialized and it cannot be changed through reassignment using the assignment operator. However, if such constant is an object, its property can either be added, updated or removed.

It is advised to declare a variable with const when you certainly know that the value will not change.

Main characteristics of const:

  • Also introduced in ES6 (2015).
  • Has block scope.
  • Cannot be redeclared or reassigned.
  • Must be initialized at the moment of declaration.
const pi = 3.1416;

// pi = 3.14; ✖️ Error: reassignment not allowed
console.log(pi); // 3.1416 gets printed
Enter fullscreen mode Exit fullscreen mode

Note: const makes the variable binding immutable, NOT the value itself.
If the value is an object or array, its contents can still change:

const myArr = [1, 2, 3];

myArr.push(4); // ✔️ allowed - modifying contents
console.log(myArr); // [1, 2, 3, 4] gets printed

myArr = [5, 6, 7]; // ✖️ error: Assignment to constant variable
Enter fullscreen mode Exit fullscreen mode

While we can modify the array's contents (adding, removing, or changing elements), we cannot reassign the entire array to a new value.


Variables Scope

A scope is the region of code where a variable is accessible. Let's think of it as the current context of execution in which values and expressions are "visible" or can be referenced.

Global Scope

Variables declared Globally, that is to say, variables declared outside any block or function, have something called Global Scope. This Global variables can be accessed from anywhere in a JavaScript program.

Variables declared with either var, let or const share certain similarities when declared outside a block. They all have Global scope:

var myName = "Martin"; //Global scope

let currentYear = 2025 // Global scope

const isDeveloper = true // Global scope
Enter fullscreen mode Exit fullscreen mode

Additional Example

This variable declared outside the function, becomes Global:

let treeName = "Ceibo";
// treeName variable can seamlessly be used here

function printTreeName() {
  // treeName variable can be used here too!
}

// treeName variable can evenly be used after the function
Enter fullscreen mode Exit fullscreen mode

Global Variables have Global Scope:

  • All scripts and functions in the same web page can access a variable with global scope.
  • In browsers, global variables declared with var automatically become properties of the window object (or globalThis in modern JavaScript).
  • Globals declared with let or const do not attach to window, but they’re still accessible everywhere in the script.
  • Too many global variables can lead to naming conflicts (two scripts using the same variable name accidentally overwrite each other).
  • Relying on globals makes code harder to debug and maintain, since any part of the program can modify them unexpectedly.
  • Best practice: limit global variables by using functions, modules, or block scope instead.

Function Scope (var)

Variables declared with var are visible throughout the entire function where they are defined — even outside blocks.

Quick example:

function testVar() {
  if (true) {
    var message = "Hello from var!";
  }
  console.log(message); // ✔️ Works (function scoped)
}

testVar(); // "Hello from var" gets printed
Enter fullscreen mode Exit fullscreen mode

However, variables defined inside a function are not accessible (visible) from outside the function. That includes var, let and const. They all have function scope.

function myDogName() {
  var dogName = "Fido"; // Function scope
  console.log(dogName);
}

function myAgeNumber() {
  var myAge = 25; // Function scope
  console.log(myAge);
}

function isFunctionScoped() {
  var functionScoped = true; // Function scope
  console.log(functionScoped);
}
Enter fullscreen mode Exit fullscreen mode

With this, we can clearly state that variables declared within a JavaScript function, are local to the function in terms of scope.

// Variable carColor cannot be used here

function getCarProps() {
  let carColor = "grey";

  // Variable carColor can seamlessly be used here
}

// Variable carColor cannot be used here
Enter fullscreen mode Exit fullscreen mode

Local Variables Have Function Scope

  • They can only be accessed from within the function.
  • No scripts or functions outside the function can access them.
  • Variables with the same name can be used outside the function.
  • Variables with the same name can be used in different functions.
  • Local variables are created when a function starts.
  • Local variables are deleted when the function is completed.
  • Arguments (parameters) work as local variables inside functions.

Block Scope (let and const)

Variables declared with let or const are only accessible inside the block ({ ... }) where they are defined. This approach provides a much better code organization and structuring, thus preventing unintended variables overwrites and unexpectedly undesired errors .

function testLetAndConst() {
  if (true) {
    let msg1 = "Hello from let";
    const msg2 = "Hello from const";
    console.log(msg1); // ✔️ Works
    console.log(msg2); // ✔️ Works
  }
  // console.log(msg1); ✖️ Error: msg1 is not defined
  // console.log(msg2); ✖️ Error: msg2 is not defined
}

testLetAndConst();
Enter fullscreen mode Exit fullscreen mode

Notes

  • Variables declared with the var keyword cannot have block scope.
  • Variables declared with the var keyword, inside a { ... } block can be accessed from outside the block.

Synthesized

  • var has the particularity that it ignores block boundaries. Nonetheless, it is indeed limited by functions.

  • let and const respect block boundaries. Thus, they are a safer, and more predictable approach.

An easy-to-follow table to grasp these scope-related concepts better:

Keyword Scope Type Redeclaration Hoisted Notes
var Function ✔️ Yes ✔️ With undefined Legacy, avoid if possible
let Block ✖️ No ✔️ But uninitialized (TDZ) Use for mutable values
const Block ✖️ No ✔️ But uninitialized (TDZ) Use by default

Hoisting

Hoisting is a default behaviour of JavaScript consisting of the process whereby the interpreter moves the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code.

Variables Declarations Are Hoisted

In JavaScript we can declare a variable after it has been used; id est, a variable can be used before it has been declared.

Both of the following examples below will provide the same result.

Example 1:

animal = "dog"; // Assigning "dog" to animal

console.log(animal); // Printing "dog" in the console

var animal; // Declaration
Enter fullscreen mode Exit fullscreen mode

Example 2:

var animal; // Declaration

animal = "dog"; // Assigning "dog" to animal

console.log(animal); // Printing "dog" in the console
Enter fullscreen mode Exit fullscreen mode

Variables declared with var will be hoisted, this means they can be referenced from anywhere in their respective scope. Nonetheless, if you try to access a variable before it has been declared, its value will eventually always default to undefined. With this in mind, we can state that only its declaration and default initialization will get hoisted, but not its value through assignment.

console.log(tree); // undefined

var tree = "Pine";

function printColor() {
  console.log(color); // undefined
  var color = "Crimson";
}

printColor();
Enter fullscreen mode Exit fullscreen mode

As an additional example:

var tree;

console.log(tree); // undefined

tree = "Pine";

function printColor() {
  var color;
  console.log(color); // undefined
  var color = "Crimson";
}

printColor();
Enter fullscreen mode Exit fullscreen mode

The let & const Hoisting Behaviour

Differently from var, the let and const keywords have their unique hoisting mechanism: They are hoisted to the top of the block (meaning JavaScript knows they exist), but they remain uninitialized until the code execution reaches their declaration line. This is different from var, which is hoisted and automatically initialized with undefined.

Attempting to use a let variable before its proper declaration will result in a ReferenceError.

Until the declaration is processed, the variable will remain in a "temporal dead zone". The Temporal Dead Zone is the period between entering the scope where the variable is declared and the actual declaration statement. During this time, the variable technically exists in memory but cannot be accessed, resulting in a ReferenceError if you try to use it.

console.log(dog); // ReferenceError
let dog = "Mastiff";

console.log(cat); // ReferenceError
let cat = "Persian";
Enter fullscreen mode Exit fullscreen mode

Let's see an additional example:

console.log(year);

displayYear();

function displayYear() {
  console.log("The year is ", year);
}

let year = 2025;
Enter fullscreen mode Exit fullscreen mode

Output:

Uncaught ReferenceError: year is not defined
Enter fullscreen mode Exit fullscreen mode

Unlike var declarations which are initialized during its creation phase with undefined as the default value, year has no reference to its value in the memory. Due to this fine detail, a reference error is thrown as seen in the example.

Now, going back to the "temporal dead zone" concept, we will examine it a little bit better:

  • Temporal dead zone in question:
  console.log(year);

  displayYear();

  function displayYear() {
    console.log("The year is ", year);
  }
Enter fullscreen mode Exit fullscreen mode
  • End of the temporal dead zone:
  let year = 2025;
Enter fullscreen mode Exit fullscreen mode

Proper way for the let methodology:

let year = 2025;
console.log(year);

function displayYear() {
  console.log("The year is ", year);
}

displayYear();
Enter fullscreen mode Exit fullscreen mode

In addition, we can also see that the temporal dead zone can provide us a clear benefits when it comes to avoiding accidental overwritings:

let fruit = "Apple";

if (true) {
  // Temporal dead zone for block-scoped fruit starts here
  console.log(fruit); // ReferenceError
  let fruit = "Banana";
}
Enter fullscreen mode Exit fullscreen mode

On the other hand, we have the const keyword:

const horseName;
console.log(horseName)
Enter fullscreen mode Exit fullscreen mode

Output:

Uncaught SyntaxError: Missing initializer in const declaration
Enter fullscreen mode Exit fullscreen mode

Since a constant cannot change its value through assignment or be re-declared while the program is running, it must be initialized with a value

Proper use of const:

const horseName = "Duke";
console.log(horseName);
Enter fullscreen mode Exit fullscreen mode

Output:

Duke
Enter fullscreen mode Exit fullscreen mode

Variable Shadowing

We call it as such when an inner-scope variable hides or overrides an outer variable within the local scope

let name = "Alice";

function printName() {
  let name = "Bob"; 
  console.log(name); // Bob
}

printName();
console.log(name); // Alice
Enter fullscreen mode Exit fullscreen mode

Two key points to keep carefully in mind:

  • var allows redeclaration, something which can lead to bugs and unexpected outcomes.
  • let and const will throw errors if you attempt to redeclare.
var x = 25.4;
var x = 32.7; // No errors, it simply gets overwritten

let y = 18.3;
let y = 49.7; // SyntaxError: Identifier 'y' has already been declared
Enter fullscreen mode Exit fullscreen mode

Considering that variable shadowing can make us lose access to the original variable, such practice can lead to unwanted bugs and unexpected results that will make it difficult to debug since the origin of the issue won't be clear to us. It is always a good idea to declare variables with a name as properly explanatory as possible, so it describes its purpose by itself and for itself. It is also recommended to declare your variables organizedly in groups where they are all near one another in order to make it easy to spot if there are repeated names, instead of senselessly spreading them across the codebase.


Bonus: Variables General Best Practices

  • Have the preference of using const by default, then let when reassignment is required. Avoid var as much as possible, since it can lead to hoisting issues and unexpected behaviours. Modern JavaScript development relies primarily on let and const.
  • Keep variables scoped as tightly as possible (block scope > function scope > global).
  • Avoid shadowing unless it’s intentional, obvious and, ultimately, useful.
  • Minimize the use of global variables. These are accessible everywhere, which can cause name collisions, hard-to-find bugs, and different sort of ungrateful surprises. Developed a liking for locally scoped declarations.
  • Initialize variables when declared for clarity. This prevents them to have an undefined value which could lead to potential errors.
  • Use descriptive names that highlight the variable's purpose or data that it holds — be it global or local, readability matters more than you think.
  • Tend to group related variables together. When they are connected logically and sensically, this improves readability and reduces shadowing risk.

Thank you for your time!

If you’d like to support my writing, you can buy me a coffee here: PayPal.me/martinxcvi


Cover photo by Desmond Marshall on Unsplash 📸

Top comments (0)