What are primitive values and reference values in JavaScript?

Primitive & Reference
Whenever you create a variable in JavaScript, that variable can store one of two types of data, a primitive value or a reference value. If the value is a number
, string
, boolean
, undefined
, null
, or symbol
, it's a primitive value. If it's anything else (i.e. typeof object
), it's a reference value.
// Primitive Values
number
string
boolean
undefined
null
symbol
// Reference Values, anything that is "typeof" "object"
objects
arrays
functions
const age = 28; // primitive
const name = 'Abhishek'; // primitive
const loading = false; // primitive
const user = undefined; // primitive
const response = null; // primitive
const counter = Symbol('counter'); // primitive
const user = { name: 'Abhishek' }; // reference
const friends = ['Rohit', 'Mohit']; // reference
const doThing = () => ({}); // reference
On the surface primitive values and reference values look the same, but under the hood they behave much differently. The key difference can be seen in how they store their value in memory. If you looked at the in-memory value of a primitive, you'd see the actual value itself (28, 'Abhishek', false, etc). If you looked at the in-memory value of a reference type, you'd see a memory address (or a "reference" to a spot in memory). In practice though, what difference does it make? Let's take a look at some examples.
let surname = 'Jakhar';
let displayName = surname;
surname = 'Abhishek';
console.log(surname); // 'Abhishek'
console.log(displayName); // 'Jakhar'
First we create a variable called surname and assign the string Jakhar to it. Then we create a new variable called displayName and assign it to whatever the in-memory value of surname is, which happens to be Jakhar. From there we change the in-memory value of surname to be Abhishek. Now, when we log surname we get Abhishek and when we log displayName we get Jakhar. Though this example demonstrates that the in-memory value of a primitive is the value itself, there's nothing surprising or really interesting going on here.
Let's look at a similar example but instead of using a primitive value, let's use a reference value.
let laddu = {
type: 'Dog',
name: 'Laddu',
};
let burfi = laddu;
burfi.name = 'Burfi';
console.log(laddu.name); // Burfi
console.log(burfi.name); // Burfi
First we create a variable called laddu and assign it to an object which has two properties, type and name. Then we create a new variable called burfi and assign it to whatever the in-memory value of laddu is, which is the reference to the spot in memory where the laddu object is located. At this point, both laddu and burfi are referencing the same spot in memory. What that means is when we modify burfi.name, because burfi and laddu are referencing the same spot in memory, it's as if we also modified laddu.name. That's why when we log laddu.name and burfi.name we get the same value, Burfi.
Let's look at one more example to cement your understanding. What do you think happens when, using the identity operator (===), we compare two primitives that have the same value?
const name = 'Abhishek';
const friend = 'Abhihek';
name === friend; // true
Here we see that because name and friend have the same value, Abhishek, when comparing them, we get true
.
This probably seems obvious but it's important to realize that the reason we get true
is because, with the identity operator, primitives are compared by their value.
Since both values equal Abhishek, comparing them evaluates to true
.
Now, what about reference values?
const laddu = {
type: 'Dog',
name: 'Laddu',
};
const burfi = {
type: 'Dog',
name: 'Burfi',
};
laddu === burfi; // false
Even though laddu and burfi have the same properties and values, when comparing them with the identity operator, we get false
.
The reason for that is because, unlike primitive values, reference values are compared by their reference, or their location in memory.
Above, even though laddu and burfi have the same properties and values, they're occupying different locations in memory.
Both these examples demonstrate how primitive types are compared by their value while reference types are compared by their reference.
An interesting by-product of primitive values is that they're always immutable. This make sense if you think of primitives in terms of their in-memory value. We said earlier that "if you looked at the in-memory value of a primitive, you'd see the actual value itself". The reason primitive values are always immutable is because whenever you change a primitive value, what you're actually doing is replacing the in-memory value. Because you can only replace the value and never modify it, by definition, that makes it immutable.