Apply immutability principles across languages for safer, more predictable code
Immutability is a cornerstone of functional programming where data cannot be modified after creation. Instead of changing existing values, you create new values. This approach eliminates entire classes of bugs related to shared mutable state, makes code easier to reason about, and enables safe concurrent programming.
Immutable data has several key benefits:
// MUTABLE APPROACH (avoid)
const user = {
name: 'Alice',
email: 'alice@example.com',
addresses: []
};
function addAddress(user, address) {
user.addresses.push(address); // Mutation!
return user;
}
// IMMUTABLE APPROACH (prefer)
const userImmutable = {
name: 'Alice',
email: 'alice@example.com',
addresses: []
};
function addAddressImmutable(user, address) {
return {
...user,
addresses: [...user.addresses, address]
};
}
// Usage
const user1 = { name: 'Bob', addresses: [] };
const user2 = addAddressImmutable(user1, '123 Main St');
console.log(user1.addresses.length); // 0 - unchanged
console.log(user2.addresses.length); // 1 - new object
// Updating nested structures
const state = {
user: {
profile: {
name: 'Charlie',
settings: {
theme: 'dark',
notifications: true
}
}
}
};
// MUTABLE (avoid)
function toggleNotificationsMutable(state) {
state.user.profile.settings.notifications = !state.user.profile.settings.notifications;
return state;
}
// IMMUTABLE (prefer)
function toggleNotificationsImmutable(state) {
return {
...state,
user: {
...state.user,
profile: {
...state.user.profile,
settings: {
...state.user.profile.settings,
notifications: !state.user.profile.settings.notifications
}
}
}
};
}
// Using Immer library for simpler deep updates
import { produce } from 'immer';
function toggleNotificationsImmer(state) {
return produce(state, draft => {
draft.user.profile.settings.notifications = !draft.user.profile.settings.notifications;
});
}// Array operations immutably
const numbers = [1, 2, 3, 4, 5];
// Adding elements
const withSix = [...numbers, 6]; // [1, 2, 3, 4, 5, 6]
const withZero = [0, ...numbers]; // [0, 1, 2, 3, 4, 5]
// Removing elements
const withoutThree = numbers.filter(n => n !== 3); // [1, 2, 4, 5]
const withoutFirst = numbers.slice(1); // [2, 3, 4, 5]
const withoutLast = numbers.slice(0, -1); // [1, 2, 3, 4]
// Updating elements
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
const updateThird = [
...numbers.slice(0, 2),
999,
...numbers.slice(3)
]; // [1, 2, 999, 4, 5]
// Object operations immutably
const person = {
firstName: 'John',
lastName: 'Doe',
age: 30
};
// Adding/updating properties
const updated = { ...person, age: 31, city: 'Boston' };
// Removing properties
const { age, ...withoutAge } = person;
// Merging objects
const contact = { email: 'john@example.com', phone: '555-1234' };
const merged = { ...person, ...contact };
// Complex transformations
const users = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false },
{ id: 3, name: 'Charlie', active: true }
];
// Update specific user immutably
function updateUser(users, id, updates) {
return users.map(user =>
user.id === id ? { ...user, ...updates } : user
);
}
const updatedUsers = updateUser(users, 2, { active: true });Persistent data structures share structure for efficiency:
# Elixir's persistent data structures share memory
defmodule Performance do
# Adding to head is O(1)
def prepend_efficient(list, item) do
[item | list]
end
# Appending to tail is O(n)
def append_slow(list, item) do
list ++ [item]
end
# Building lists efficiently: collect then reverse
def build_list_efficiently(n) do
0..n
|> Enum.reduce([], fn i, acc -> [i | acc] end)
|> Enum.reverse()
end
# Maps are persistent hash trees - efficient updates
def update_map_efficiently(map, key, value) do
Map.put(map, key, value) # O(log n)
end
end
# Structural sharing example
original_map = %{a: 1, b: 2, c: 3, d: 4}
updated_map = %{original_map | a: 100}
# Only the changed part is new; rest is sharedThe guidance above uses JavaScript for the primary examples. For the same patterns in other languages and deeper material, see:
Treat data as values, not containers to mutate. Produce a new copy for every change and treat every argument as read-only. Know when not to: a local variable never shared outside its function is fine to mutate for a tight loop.
state.user.name = "x";return { ...state, user: { ...state.user, name: "x" } };const next = JSON.parse(JSON.stringify(state)); next.a.b = 1;const next = { ...state, a: { ...state.a, b: 1 } };Object.freeze it, or return a fresh copy per caller.a1083f4
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.