Disclaimer: I am not a professional software engineer nor a computer science student. This is more of what I understand about variables in JavaScript from what I read on MDN Web Docs.
JavaScript Variables
A variable is like a label that points to a certain space in the computer's memory. When you assign a data type or object to a variable, it goes into that space.
Declaring Variables
A variable is declared using one of these three keywords: var, let or const.
- var — unpredictable and highly recommended NOT to use
- let — use only when you are 100% sure you will reassign a new value later on
- const — the default choice; always use this unless you need to reassign
Naming Variables
There are a few rules and best practices to follow when naming variables:
- Names can only start with a letter, underscore (
_) or dollar sign ($) — any other character will throw an error - Names cannot contain spaces. For example,
let my name = "Athena"is not allowed - Names are case sensitive —
num,NumandNUMare three different variable names - Always make sure your variable name briefly describes what its value represents
- Your variable names must not be the same as JavaScript keywords like;
let let = "hey";
//Error: let is not allowed to be used in variable declarations
For variables with two words, use camelCase or underscores:
let myName = "Athena"; // camelCase
let my_name = "Athena"; // underscore
const MY_NAME = "Athena"; //for const it is recommended to use UPPERCASE_SNAKE_CASE
Declaring Multiple Variables
You can declare two variables on the same line:
let num2 = 2, num4 = 4;
However, for best practices declare them on separate lines to improve readability, like;
let num2 = 2;
let num4 = 4;
Reassigning Variables
A variable declared with let or var can have its value reassigned, but a variable declared with const cannot:
let num4 = 77;
num4 = 55;
console.log(num4);
// 55 — the value of num4 is now reassigned to 55
const num2 = 45;
num2 = 46;
console.log(num2);
// Uncaught TypeError: Assignment to constant variable.
Also, you cannot create another variable or function with the same name as a const variable, making your code more predictable and less prone to bugs. This is why it is highly recommended to always use const by default — it prevents accidental reassignment and signals to other developers that a value won't change throughout your code.
Primitives vs Objects in Memory
A variable can only point to one value at a time, but that value can be a data structure such as an array or object that contains multiple values. Primitives are passed by value and objects are passed by reference.
Primitive data types — many variables can hold the same primitive value, but they are independent copies. Each copy takes up its own space in memory, so changing one never affects the others:
let num4 = 45;
let num5 = num4;
num4 = num4 + 5;
console.log(num4, num5);
// 50 45 — num5 is still 45 even though num4 changed
Objects — many variables can label one object, but it is one single object in memory. Changing it through one variable affects all variables pointing to it:
let cup1 = { drink: "water" };
let cup2 = cup1;
console.log(cup1.drink, cup2.drink);
// water water
cup2.drink = "coffee";
console.log(cup1.drink, cup2.drink);
// coffee coffee — cup1 is also changed
Analogy:
For primitive data types: think of a variable as a label on a cup, where the cup is the value. If you buy a second cup that looks identical to the first, it has its own separate label and takes up its own space in the cupboard — they are completely independent.For objects: there is only one cup and multiple labels can be stuck onto that same cup. So when you fill or empty the cup, it affects everything pointing to it.
Mutating Objects Declared with const
While you cannot reassign a const variable to point to a new object, you can still change the contents of the object it points to:
//Example 1
const cup2 = { drink: "water" };
cup2.drink = "coffee";
console.log(cup2.drink);
// coffee — modifying a property is allowed
cup2 = { drink: "juice" };
console.log(cup2);
// Uncaught TypeError: Assignment to constant variable.
// — reassigning the variable itself is not allowed
//Example 2
const cup = ["1 tsp coffee", "2 tsp sugar"];
cup[0] = "1 tsp Milo";
console.log(cup[0]);
// 1 tsp Milo - modifying an item of the array is allowed
cup = ["juice", "ice"];
console.log(cup);
// Uncaught TypeError: Assignment to constant variable.
// — reassigning the variable itself is not allowed
Analogy:
Think of the data structure or array as a cup, now when you change the contents in the cup you are not changing the cup itself but the contents it carries. So with const keyword you cannot change the cup itself but you can change the contents of the cup like whether it can have ice or not
There is a catch though. If you empty an array or object not through methods like pop() or splice() but like this:
const abc = ['a', 'b'];
abc = [];
console.log(abc);
// Uncaught TypeError: Assignment to constant variable.
// — reassigning the variable itself is not allowed
This error is thrown because the computer sees [ ] as a brand new empty array in memory, not a modification of the existing one. And it is right — you are not emptying the contents of the original array, you are pointing the variable - abc to a completely new empty data structure, which const does not allow.
Now that we understand how variables store and hold data, let us look at where in your program they can actually be accessed
Scope
JavaScript has four kinds of scope: global scope, module scope, function scope and block scope.
Global Scope
A global scope is the outermost part of a program. Any variable declared before a function, class, loop, or any other structure is in the global scope and can be accessed from anywhere in the program. A variable declared within the global scope is a global variable:
const greeting = "Hello";
function sayHello() {
console.log(greeting); // ✅ accessible inside the function
}
if (true) {
console.log(greeting); // ✅ accessible inside the if statement
}
sayHello();
console.log(greeting); // ✅ accessible outside everything
// Hello
Module Scope
A variable declared within a module can only be accessed in that module. To use it elsewhere it must be explicitly exported and then imported:
// moduleA.js
const greeting = "Hello";
export default greeting; // has to be exported to be used elsewhere
// moduleB.js
console.log(greeting); // ❌ ReferenceError: greeting is not defined
// greeting is confined to moduleA.js and cannot be accessed here directly
import greeting from './moduleA.js'; // ✅ has to be imported first
console.log(greeting); // Hello
Function Scope
A variable declared within a function is a local variable and can only be accessed inside that function. It cannot be accessed from outside:
function sayHello() {
const greeting = "Hello";
console.log(greeting); // ✅ accessible inside the function
}
sayHello();
console.log(greeting); // ❌ ReferenceError: greeting is not defined
However, a variable declared within a function can be accessed by any inner if else statements or loops inside that same function:
function sayHello() {
const greeting = "Hello";
if (true) {
console.log(greeting); // ✅ accessible inside the if statement
}
for (let i = 0; i < 3; i++) {
console.log(greeting); // ✅ accessible inside the loop
}
}
sayHello();
console.log(greeting); // ❌ ReferenceError: greeting is not defined
Block Scope
A block scope is defined by a pair of curly braces {}, as seen in structures like if else statements and loops. Variables declared with let or const are confined to the block in which they are defined and cannot be accessed outside of it.
if (true) {
const greeting = "Hello";
console.log(greeting); // ✅ accessible inside the if statement
}
console.log(greeting); // ❌ ReferenceError: greeting is not defined
for (let i = 0; i < 3; i++) {
const message = "Hi";
console.log(message); // ✅ accessible inside the loop
}
console.log(message); // ❌ ReferenceError: message is not defined
var however ignores block boundaries and leaks out into the surrounding scope, which is another reason why using var is strongly discouraged:
if (true) {
var greeting = "Hello";
}
console.log(greeting); // ⚠️ Hello — var leaks outside the if statement
for (var i = 0; i < 3; i++) {
// loop runs
}
console.log(i); // ⚠️ 3 — var leaks outside the loop
A Note on Variable Naming Across Scopes
It is best practice to avoid using the same variable name in different scopes. It can be confusing both to yourself and to other developers reading your code. Note that this is mainly a concern when using let — const handles this for you by throwing an error if you try to declare a variable with the same name in the same scope.
Hoisting
When JavaScript runs your code, it processes variable declarations before executing anything else. This is called hoisting.
With var, the variable is hoisted and automatically set to undefined right away, meaning you can reference it before its declaration — though the value won't be useful until you actually assign something to it:
console.log(greeting); // ⚠️ undefined — no error thrown, but value is not yet assigned
var greeting = "Hello";
console.log(greeting); // Hello
let and const behave differently. There is a period of time between when a scope starts and when a let or const variable is actually declared — this is called the Temporal Dead Zone (TDZ). During this period the variable exists but has not been initialized yet, so trying to access it throws a ReferenceError:
console.log(greeting); // ❌ ReferenceError: Cannot access 'greeting' before initialization
const greeting = "Hello";
console.log(message); // ❌ ReferenceError: Cannot access 'message' before initialization
let message = "Hi";
This is actually a good thing — the TDZ forces you to declare your variables before using them, making your code more predictable and bugs easier to track down. It is yet another reason why var should be avoided in favour of let and const.
Unlike var, which only hoists the declaration but not its value, function declarations are hoisted entirely — meaning you can safely call a function anywhere within its scope, even before it appears in the code:
sayHello(); // ✅ Hello — works fine even though the function is declared below
function sayHello() {
console.log("Hello");
}
Function expressions follow the same hoisting rules as the variable keyword used to declare them — unlike function declarations which are hoisted entirely.
If declared with var — it is hoisted but only as undefined, so calling it before declaration throws a TypeError:
javascriptsayHello(); // ❌ TypeError: sayHello is not a function
var sayHello = function() {
console.log("Hello");
}
If declared with let or const — it falls in the TDZ, so calling it before declaration throws a ReferenceError:
javascriptsayHello(); // ❌ ReferenceError: Cannot access 'sayHello' before initialization
const sayHello = function() {
console.log("Hello");
}
To read more on variables in general you can check out this link from imarticus.org: Demystifying Variables: The Building Blocks of Programming
Top comments (0)