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
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 let
statement 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;
In a technical sense, this varName
variable is undefined
, so we'll assign it a value:
varName = "House";
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";
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
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
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
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
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
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
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 thewindow
object (orglobalThis
in modern JavaScript). - Globals declared with
let
orconst
do not attach towindow
, 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
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);
}
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
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();
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
andconst
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
Example 2:
var animal; // Declaration
animal = "dog"; // Assigning "dog" to animal
console.log(animal); // Printing "dog" in the console
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();
As an additional example:
var tree;
console.log(tree); // undefined
tree = "Pine";
function printColor() {
var color;
console.log(color); // undefined
var color = "Crimson";
}
printColor();
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";
Let's see an additional example:
console.log(year);
displayYear();
function displayYear() {
console.log("The year is ", year);
}
let year = 2025;
Output:
Uncaught ReferenceError: year is not defined
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);
}
- End of the temporal dead zone:
let year = 2025;
Proper way for the let
methodology:
let year = 2025;
console.log(year);
function displayYear() {
console.log("The year is ", year);
}
displayYear();
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";
}
On the other hand, we have the const
keyword:
const horseName;
console.log(horseName)
Output:
Uncaught SyntaxError: Missing initializer in const declaration
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);
Output:
Duke
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
Two key points to keep carefully in mind:
-
var
allows redeclaration, something which can lead to bugs and unexpected outcomes. -
let
andconst
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
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, thenlet
when reassignment is required. Avoidvar
as much as possible, since it can lead to hoisting issues and unexpected behaviours. Modern JavaScript development relies primarily onlet
andconst
. - 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)