yanko-belov

immutability

5
0
# Install this skill:
npx skills add yanko-belov/code-craft --skill "immutability"

Install specific skill from multi-skill repository

# Description

Use when modifying objects or arrays. Use when tempted to mutate function parameters. Use when state changes cause unexpected bugs.

# SKILL.md


name: immutability
description: Use when modifying objects or arrays. Use when tempted to mutate function parameters. Use when state changes cause unexpected bugs.


Immutability

Overview

Never mutate. Always return new objects.

Mutation causes bugs that are hard to track - when objects change unexpectedly, debugging becomes a nightmare. Immutability makes state changes explicit and predictable.

When to Use

  • Modifying objects or arrays
  • Updating state in React/Redux
  • Function receives object as parameter
  • Data shared between multiple parts of code

The Iron Rule

NEVER mutate objects or arrays. ALWAYS return new copies with changes.

No exceptions:
- Not for "it's more efficient"
- Not for "it's simpler"
- Not for "it's just one property"
- Not for "no one else uses this object"

Detection: Mutation Smell

If you're modifying an object directly, STOP:

// ❌ VIOLATION: Direct mutation
function updateUserAddress(user: User, newCity: string): User {
  user.address.city = newCity;  // Mutates original!
  return user;
}

// ❌ VIOLATION: Array mutation
function addItem(cart: CartItem[], item: CartItem): CartItem[] {
  cart.push(item);  // Mutates original!
  return cart;
}

// ❌ VIOLATION: Nested mutation
function updateSettings(config: Config): Config {
  config.settings.theme = 'dark';  // Deep mutation!
  return config;
}

Problems:
- Original object changed unexpectedly
- React won't detect changes (same reference)
- Debugging nightmare - "who changed this?"
- Undo/redo impossible

The Correct Pattern: Return New Objects

// βœ… CORRECT: Return new object
function updateUserAddress(user: User, newCity: string): User {
  return {
    ...user,
    address: {
      ...user.address,
      city: newCity
    }
  };
}

// βœ… CORRECT: Return new array
function addItem(cart: CartItem[], item: CartItem): CartItem[] {
  return [...cart, item];
}

// βœ… CORRECT: Deep immutable update
function updateSettings(config: Config): Config {
  return {
    ...config,
    settings: {
      ...config.settings,
      theme: 'dark'
    }
  };
}

// Usage - originals unchanged
const user = { name: 'Alice', address: { city: 'Boston' } };
const updatedUser = updateUserAddress(user, 'Cambridge');

console.log(user.address.city);        // 'Boston' - unchanged!
console.log(updatedUser.address.city); // 'Cambridge' - new object

Immutable Operations

Objects

// Update property
const updated = { ...obj, property: newValue };

// Remove property
const { removed, ...rest } = obj;

// Merge objects
const merged = { ...obj1, ...obj2 };

Arrays

// Add item
const added = [...arr, newItem];
const prepended = [newItem, ...arr];

// Remove item
const removed = arr.filter(item => item.id !== idToRemove);

// Update item
const updated = arr.map(item => 
  item.id === id ? { ...item, ...changes } : item
);

// Sort (creates new array)
const sorted = [...arr].sort((a, b) => a.value - b.value);

Nested Updates

// Deep update helper
const updated = {
  ...state,
  users: {
    ...state.users,
    [userId]: {
      ...state.users[userId],
      name: newName
    }
  }
};

// Or use immer for complex updates
import { produce } from 'immer';

const updated = produce(state, draft => {
  draft.users[userId].name = newName;  // Looks mutable, but isn't
});

Pressure Resistance Protocol

1. "It's More Efficient"

Pressure: "Creating new objects is wasteful"

Response: Modern JS engines optimize this. The bugs from mutation cost more than the memory.

Action: Use immutable patterns. Profile if you suspect performance issues.

2. "It's Simpler"

Pressure: "Direct mutation is easier to read"

Response: Simple to write, hard to debug. Immutability is simpler in the long run.

Action: Spread operators are not complex. Use them.

3. "It's Just One Property"

Pressure: "I'm only changing one field"

Response: One mutation sets a precedent. Others follow. Bugs multiply.

Action: All updates return new objects. No exceptions.

4. "No One Else Uses This"

Pressure: "This object is local, mutation is safe"

Response: Code evolves. Local becomes shared. Build the habit now.

Action: Always immutable, regardless of current usage.

Red Flags - STOP and Reconsider

  • obj.property = value (direct assignment)
  • array.push(), array.pop(), array.splice()
  • array.sort() without spreading first
  • Modifying function parameters
  • delete obj.property

All of these mean: Rewrite immutably.

Quick Reference

Mutable (Bad) Immutable (Good)
obj.x = y { ...obj, x: y }
arr.push(x) [...arr, x]
arr.pop() arr.slice(0, -1)
arr.splice(i, 1) arr.filter((_, idx) => idx !== i)
arr.sort() [...arr].sort()
delete obj.x const { x, ...rest } = obj

Common Rationalizations (All Invalid)

Excuse Reality
"More efficient" Bugs cost more than memory.
"Simpler" Simpler to write, harder to debug.
"Just one property" One exception leads to many.
"No one else uses it" Code changes. Be consistent.
"Too verbose" Spread syntax is concise.
"React handles it" React needs new references to detect changes.

The Bottom Line

Never mutate. Spread and return new objects.

Every modification returns a new object. Original data stays unchanged. This enables debugging, undo/redo, React change detection, and sanity.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.