JavaScript Immutability

Mahmood Nalim
7 min readOct 21, 2023

What’s immutability?

📍Meaning of immutability in English:

the state of not changing, or being unable to be changed.

📍What’s immutability in JavaScript?

There are generally two approaches to changing data.

  1. mutate the data by directly changing the data’s values.
  2. Replace the data with a new copy that has the desired changes.

Mutability describes whether the state of an object can be modified after its declaration or not.

Immutability is Whenever we want to make changes to some data (for example to an object or an array) we should get a new object back with the updated data instead of directly modifying the original one.

In JavaScript, only objects and arrays are mutable, not primitive values.

Let’s break it down

JavaScript is a weakly typed language, which means it allows implicit type conversion when an operation involves mismatched types, instead of throwing type errors.

const foo = 42; // foo is a number
const result = foo + "1"; // JavaScript coerces foo to a string, so it can be concatenated with the other operand
console.log(result); // 421

Strict mode -

Strict mode makes several changes to normal JavaScript semantics:

  1. Eliminates some JavaScript silent errors by changing them to throw errors.
  2. Fixes mistakes that make it difficult for JavaScript engines to perform optimizations
  3. To invoke strict mode for an entire script, put the exact statement "use strict";

Template literals -

Template literals are literals delimited with backtick (`) characters, allowing for multi-line strings, and string interpolation with embedded expressions.

multi-line strings -

String interpolation -

const a = 5;
const b = 10;
console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
// "Fifteen is 15 and
// not 20."
const a = 5;
const b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

Data types can be a bit of a mind boggling concept. But as programmers, we use data types everyday — so they’re something we should understand.

Primitive Types

Value types are been stored on the Stack in our memory. The stack is simply a stack of data that has a “LIFO” (last in, first out) data structure.

When storing a value type in memory, it adds an element to the top of the stack with the value of the newly created variable.

The first variable — name gets into the stack with the value of the variable data. Then, the newName gets into the stack in a new memory location with the value of the variable data.

Look into the below code to understand the concept.

Reference Types

reference types are been stored on the Heap. The Heap, indifferent from the stack, has no order of how to store the data. You can think of it as it stores the data randomly, where each of the data has its own address. It is slower to access but has much more space since it handles more complex variables.

When you create a variable and assign it a value that is a reference data type, the computer does not directly store that data type in that variable (as is the case with primitive types).

What you have assigned to that variable is a pointer that points to the location of that data type in memory.

Important Note:

As you can see in the image above, we have two data structures now. A stack, and a heap. Say we declared an object, for example. The object itself is stored on a heap, and its pointer is stored on a stack. The pointer is identified by the object’s variable name, and points to that object.

Now, we could create a variable, object1, and assign an object to it. What if like before, we create another variable object2, and assign it to object1. Does that mean another object will be created on the heap? The answer is no.

Since the object already exists on the heap, object2 and object1 will both point to the same object.

Now you know the difference between primitive and reference data types.

Therefore, when changing the data of a created object, all other objects that point to the same address location on the heap are being changed also.

With that in mind, we can say that a Primitive type is immutable whereas a Reference type is mutable.

Mutable Array Methods

Certain array methods will mutate the array they’re used on:

  • push (add an item to the end)
  • pop (remove an item from the end)
  • shift (remove an item from the beginning)
  • unshift (add an item to the beginning)
  • sort
  • reverse
  • splice

Immutable Array Methods

  • slice
  • from
  • map
  • filter

The easiest thing to do, if you need to use one of these operations, is to make a copy of the array and then operate on the copy. You can copy an array with any of these methods:

Immutability in React 🧑‍💻

  1. Immutability gives stricter control over your data immediately making your code safer and more predictable. In other words, immutable objects allow you to control the interface and data flow in a predictable manner, discovering the changes efficiently.
  2. It also makes it easier to implement complex features such as undo/redo, time travel debugging, optimistic updates, and rollback
  3. By default, all child components re-render automatically when the state of a parent component changes. This includes even the child components that weren’t affected by the change. Although re-rendering is not by itself noticeable to the user (you shouldn’t actively try to avoid it!), you might want to skip re-rendering a part of the tree that clearly wasn’t affected by it for performance reasons. Immutability makes it very cheap for components to compare whether their data has changed or not.
  4. Components can take advantage of this and intelligently re-render itself only when needed. This can significantly boost performance.

React memo

memo lets you skip re-rendering a component when its props are unchanged.

This memoized version of your component will usually not be re-rendered when its parent component is re-rendered as long as its props have not changed.

In this example, notice that the Greeting component re-renders whenever name is changed (because that’s one of its props), but not when address is changed (because it’s not passed to Greeting as a prop):

memo vs useMemo

memo is a higher-order component to memoize an entire functional component. useMemo is a react hook to memoize a function within a functional component.

Should you add memo everywhere?

Optimizing with memo is only valuable when your component re-renders often with the same exact props, and its re-rendering logic is expensive. If there is no perceptible lag when your component re-renders, memo is unnecessary. Keep in mind that memo is completely useless if the props passed to your component are always different, such as if you pass an object or a plain function defined during rendering. This is why you will often need useMemo and useCallback together with memo.

In practice, you can make a lot of memoization unnecessary by following a few principles:

  1. When a component visually wraps other components, let it accept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don’t need to re-render.
  2. Prefer local state and don’t lift state up any further than necessary. For example, don’t keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library.

lift state up -Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code.

--

--