Introduction
Ever wondered what goes on inside the computer when you create and mutate variables? What exactly is the difference between let
and const
? Why you’re able to alter an object or array even after declaring it with const
?
In this post, I’ll break down all these concepts, focusing on mutability of the different data types with simple diagrams to illustrate how variables work in JavaScript.
What is mutability?
Mutability refers to whether a data type’s value can be changed after it’s been created. This is a fundamental concept that separates the two main categories of data types in the JavaScript: primitive types and object types.
To better understand this concept, lets take a moment to understand what variables really are and how they work in JavaScript
What are variables?
In modern JavaScript, values are created and stored as a variable using the let
& const
keywords. both of which acts as pointers to a location in the computer’s memory where a value is stored. The only difference is how flexible they can be. let
is flexible as it can be moved to point to another location in memory, different from the one it was initialized with but const
can only point to the address it was initialized with.
Now let’s get back to understanding the mutability of primitive and object data types in JavaScript.
Mutability of primitive data types
Primitive types are immutable meaning their values cannot be altered after creation. When you perform an operation on a primitive, you are not changing the original value, you are simply creating a new one.
Some of the common primitive data types are:
- String
- Number
- Boolean
- Null
- Undefined
Let’s take a look at a few examples to understand;
let x = 5;
x = 10;
console.log(x); // 10
From the above code snippet, you might think “didn’t we just mutate the value of x from 5 to 10?” Well, we didn’t.
The image below can better illustrate what actually happened inside the computer
What we did was move the pointer from the location in the computer memory that stored the value 5 to another location that stored the value 10. You will notice that the value 5 is still being stored at its original location but now nothing is pointing to it so it will get flushed out by JavaScript’s garbage collector to create space for another value to occupy that spot later on.
What happens when we try this with const
variable?
const x = 5;
x = 10; // Uncaught TypeError: Assignment to constant variable.
console.log(x); // This will not run
When we try to move the pointer to another location, we get an error because variables initialized with const
cannot be moved to another location in memory and because primitives are immutable, there is no way for us to change the value 5 to another value.
Here’s another example with strings
let msg = "hello";
msg.toUpperCase(); // This method returns a new string "HELLO"
console.log(msg); // The original string remains "hello"
From the above, you might expect the msg variable to become upper cased but it won’t because of the immutability of strings. However, the method toUpperCase()
returns a new string value with all the letters in uppercase which we can re-assign back to the msg variable.
let msg = "hello";
msg = msg.toUpperCase(); // The return value is re-assigned to msg
console.log(msg); // "HELLO"
Below is a diagram to help illustrate;
You will notice the original “hello” string was not modified but a new string with all the letters in uppercase was created and the msg pointer was moved to the new location storing the upper-cased value.
Mutability of object types
Object types are mutable, meaning their properties can be changed without reassigning the variable. This includes:
- Objects
- Arrays
- Functions
When you have a variable that holds an object, it’s not holding the object itself, but a reference to its location in computer memory. Imagine it like a label on a storage box. The variable is the label, and the object is the contents inside the box.
Because objects are mutable, you can change their contents directly, without changing or moving the label.
Consider this example below;
let bio = { name: "John", age: 30 };
console.log(bio.age); // 30
bio.age = 50; // We're directly changing a property on bio
console.log(bio.age); // This will log 50!
From the above, we can see that modifying the object’s properties directly changes the data at that memory location. We didn’t have to re-assign the bio variable to point to another location, we simply altered the value that it was pointing to.
The diagram below help illustrate that point;
The bio variable points to the same address in both figures, but the contents inside changed. It doesn’t matter if we used let
or const
to initialize the variable. Since we’re not moving the pointer, altering the contents of the object would still work the same.
Where we’ll run into issues is when we initialize the variable with const
and then try to re-assign it to another value (point to another location)
const bio = { name: "John", age: 30 };
bio = { name: "John", age: 50 }; // Throws an error
console.log(bio.age); // This will not run
Here, we tried to re-assign bio to another object but run into an error, because bio was initialized with const
. We would still run into the same error if we tried to re-assign it to a primitive type. This would work fine if we initialized the variable with let
and then re-assign it to another value regardless of the data type we are re-assigning to.
Let’s consider another example with an array which is a type of object;
const colors = ['red', 'yellow', 'blue'];
/* We can add or remove items directly from the 'colors' array
using methods that mutate it, like .push() or .pop() */
colors.push('green');
console.log(colors); // Output: ['red', 'yellow', 'blue', 'green']
Similar to the bio variable, we did not move the colors pointer but mutated the contents of the address it was pointing to using the .push()
method. This is the essence of mutability: the ability to change the contents of a data structure without creating a new one.
Conclusion
Understanding mutability is a milestone in any JavaScript developer’s journey. By now, you should have a clear understanding that primitives are immutable; operations on them create new values while objects are mutable, allowing you to change their contents directly.
Variables are pointers and declaring a variable with let
means you can point it to another location in memory after creation but those declared with const
are immovable and always points to the same location in memory.
In subsequent posts I will explore other concepts such as assigning by value and assigning by reference and embracing an immutable-first approach, especially with objects and arrays, to prevent unexpected side effects and simplify your code.
Thank you for reading.
Top comments (0)