0%
Reading Settings
Font Size
18px
Line Height
1.5
Letter Spacing
0.01em
Font Family
Table of contents
Make Our Utils Functions Immutable
Software Engineer
Software Engineer
I chose JavaScript for this blog because it has both mutable and immutable objects and methods, while language like Haskell enforces immutability everywhere
1. Mutable vs Immutable Objects and Methods
When we say an object is immutable, we mean it cannot be changed after it’s created. Strings in JavaScript are an example:
// language: javascript const s = "hello" console.log(s[0]) // "h" s[0] = "H" console.log(s) // "hello"
No matter what you do, the original string stays the same. Arrays, on the other hand, are mutable:
// language: javascript const nums = [1, 2, 3] nums[0] = 4 console.log(nums) // [4, 2, 3]
Many array methods also mutate the array. For example:
// language: javascript const numbers = [3, 1, 2] numbers.sort() console.log(numbers) // [1, 2, 3] - original array changed!
Besides, modern JavaScript provides immutable alternatives, like toSorted() :
// language: javascript const numbers = [3, 1, 2] const sorted = numbers.toSorted() console.log(sorted) // [1, 2, 3] console.log(numbers) // [3, 1, 2] - original stays the same
2. Take a look at this example
// language: javascript
const countEven = (arr) => {
let count = 0
while (arr.length > 0) {
if (arr.pop() % 2 === 0) count += 1
}
return count
}
const a = [1, 2, 3, 4]
console.log(countEven(a))
console.log(a)Wait 20 seconds and guess the result :D
20 seconds
10 seconds
5 seconds
end!
// language: javascript const a = [1, 2, 3, 4] console.log(countEven(a)) // 2 console.log(a) // []
Why? pop() is mutable, it removes items from the original array. The function works, but it destroys the input, which is usually not what you want.
3. Make it immutable
We can rewrite the same function without touching the input:
// language: javascript
const countEven = (arr) => {
return arr.filter(n => n % 2 === 0).length
}
const a = [1, 2, 3, 4]
console.log(countEven(a)) // 2
console.log(a) // [1, 2, 3, 4]4. Quick Tips for Writing Immutable Utils
Avoid mutating input arrays or objects
Methods like pop(), shift(), splice() , sort(), and reverse() change the original. Use immutable alternatives:
| Mutable | Immutable alternative | |-----------------------|-----------------------------------| | arr.pop() | arr.slice(0, -1) | | arr.shift() | arr.slice(1) | | arr.splice(start, n) | arr.toSpliced(start, n) | | arr.sort() | arr.toSorted() | | arr.reverse() | arr.toReversed() |
Prefer map, filter, and reduce for transformations
Instead of manually looping and modifying:
// language: javascript
// Mutable (avoid)
for (let i = 0; i < arr.length; i++) {
arr[i] *= 2
}
// Immutable
const doubled = arr.map(x => x * 2)Use readonly in TypeScript
This signals clearly that the function does not modify its input:
// language: javascript
function normalizeUsers(users: readonly User[]) {
return users.map(u => ({ ...u, name: u.name.trim() }))
}Trying to mutate users will result in a compile-time error.
Return new objects/arrays instead of mutating
Always assume your utility might be reused elsewhere. Avoid side effects:
// language: javascript
// Bad: modifies input
function addUser(users, user) {
users.push(user)
return users
}
// Good: returns new array
function addUser(users, user) {
return [...users, user]
}Deep immutability when needed
If your function handles nested objects and you want safety across all levels:
// language: javascript
const newUsers = users.map(u => ({
...u,
address: { ...u.address }
}))Use linting rules to enforce immutability
Tools like ESLint can prevent accidental mutations:
// language: javascript
"no-param-reassign": ["error", { "props": true }],
"functional/immutable-data": "error"This helps large teams maintain predictable utilities and avoid subtle bugs.
5. Summary
In short, immutable utilities are safer because they don’t unexpectedly change their inputs. While mutable methods can be fine for local variables or performance-critical code, they should be avoided in shared utilities. By keeping our utility functions immutable, our code becomes predictable, easier to reason about, which is especially important in larger projects.
Related blogs
Speed Up Independent Queries Using Rails load_async
When you're building a dashboard, it's common to fetch multiple, independent datasets. The page loading might be slow because it has to fetch all data to render a page. A common solution is using AJAX to load pieces of the dashboard, which is great, ...
Software Engineer
Software Engineer
Hello Golang: My First Steps to the Language
I’ve worked with Ruby in several projects, which is defined as "a programmer’s best friend" for its clean and English-like syntax. While my back-end experience is rooted in the Ruby on Rails framework, I prefer TypeScript for building CLI tools and s...
Software Engineer
Software Engineer