Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add Ariel-Rodriguez/programming-skills --skill "ps-minimize-mutation"
Install specific skill from multi-skill repository
# Description
Mutation must be controlled and localized. Use when reviewing code with scattered mutations or shared mutable state.
# SKILL.md
name: ps-minimize-mutation
description: Mutation must be controlled and localized. Use when reviewing code with scattered mutations or shared mutable state.
severity: WARN
Minimize Mutation
Principle
Mutation should be:
- Localized: Confined to small, clear scopes
- Explicit: Visible at call sites when mutation occurs
- Controlled: Used only when beneficial for performance or clarity
Benefits:
- Predictability: Easier to reason about code when values don't change unexpectedly
- Thread Safety: Immutable data is inherently safe for concurrent access
- Debugging: Fewer moving parts, simpler data flow
- Testing: Pure operations are easier to test
When to Use
Use this skill when:
- Reviewing code with shared mutable state
- Functions modify their arguments unexpectedly
- Objects change state across multiple locations
- Tracking down bugs related to unexpected data changes
Indicators you need this:
- Functions with surprising side effects
- Collections modified while being iterated
- Object properties changed far from creation
- Multiple functions sharing and modifying the same structure
- Debugging sessions tracing when a value changed
Instructions
Prefer Immutable Updates
Create new values instead of modifying existing ones:
- Return new collections rather than mutating inputs
- Use spread operators, Object.assign, or copy methods
- Build new objects with updated properties
Localize Mutation When Necessary
When mutation is appropriate (performance, builder patterns):
- Keep mutations within a single function scope
- Don't expose mutable references outside the scope
- Return immutable results to callers
Make Mutation Explicit
Signal mutation at function boundaries:
- Name functions to indicate mutation (update, modify, add)
- Document when parameters are mutated
- Avoid mutating function parameters received from callers
Acceptable Mutation Contexts
Mutation is reasonable when:
- Building large data structures incrementally (builders)
- Performance-critical loops processing arrays
- Local variables within a function (not escaping scope)
- Implementing caches or memoization
- Managing UI component state
Mutation should be avoided when:
- Functions operate on shared data structures
- Arguments could be reused by the caller
- The mutation affects code outside current scope
- Immutable alternatives have negligible cost
Examples
โ Good: Immutable updates
FUNCTION addItem(cart, newItem):
RETURN {
...cart,
items: [...cart.items, newItem],
total: cart.total + newItem.price
}
FUNCTION removeItem(cart, itemId):
updatedItems = cart.items.filter(item => item.id !== itemId)
updatedTotal = updatedItems.sum(item => item.price)
RETURN {
...cart,
items: updatedItems,
total: updatedTotal
}
// Original cart unchanged
// New cart returned
// Caller decides what to do with result
Functions create new values. Original data preserved. Safe for concurrent access.
โ Bad: Mutation scattered
FUNCTION addItem(cart, newItem):
cart.items.push(newItem) // Mutates input
cart.total = cart.total + newItem.price // Mutates input
RETURN cart
FUNCTION processOrder(cart):
addItem(cart, { id: 1, price: 10 }) // Mutates cart
addItem(cart, { id: 2, price: 20 }) // Mutates cart again
// Later... is cart the original or modified?
// Which functions modified it?
// Hard to track changes
// Shared mutable state
// Unpredictable behavior
// Hard to test or reason about
Functions modify shared state. Caller's data changed without clear signal. Race conditions possible.
Common Patterns
Immutable Collection Updates
Arrays:
- Add:
[...array, newItem] - Remove:
array.filter(item => item !== toRemove) - Update:
array.map(item => item.id === id ? updated : item) - Slice:
array.slice(start, end)
Objects:
- Add/Update property:
{...obj, key: newValue} - Remove property:
{...obj}; delete result.keyor use destructuring - Nested update:
{...obj, nested: {...obj.nested, key: value}}
Maps/Sets:
- Create new instances with changes
- Or use within a local scope and return frozen copy
Builder Pattern for Complex Construction
When building complex objects step-by-step:
- Use a builder class with mutation methods
- Return immutable result from
.build()method - Builder is local, result is shared immutably
Copy-on-Write
- Clone before modification for shared structures
- Preserve original reference for other consumers
- Common in Redux, React state updates
AI Review Checklist
Before accepting code, verify:
- [ ] Are function parameters mutated? Could new values be returned instead?
- [ ] Do collection operations modify the original collection?
- [ ] Is shared state mutated from multiple locations?
- [ ] Are mutations clearly signaled by function names?
- [ ] Is locally-scoped mutation actually beneficial here?
- [ ] Could immutable alternatives simplify the logic?
If function mutates parameters โ SUGGEST returning new value (SUGGEST)
If shared object is mutated โ WARN about unpredictable behavior (WARN)
If collection is modified during iteration โ WARN about bugs (WARN)
Language-Specific Guidance
JavaScript/TypeScript
- Use
constby default to prevent reassignment - Spread syntax for shallow copies:
[...array],{...object} - Array methods:
.map(),.filter(),.slice()(non-mutating) - Avoid:
.push(),.pop(),.splice(),.sort()on shared arrays - Immutable libraries: Immer, Immutable.js for deep structures
Python
- Tuples for immutable sequences
dataclasses(frozen=True)for immutable objects- List comprehensions create new lists
.copy()for shallow copies,copy.deepcopy()for deep- Avoid mutating lists/dicts passed as arguments
Java
finalfor reference immutability- Immutable collections:
List.of(),Set.of(),Map.of() - Records (Java 14+) for immutable data classes
- Defensive copies in constructors/getters
- Stream API for functional transformations
Go
- Value semantics for structs (copies by default)
- Don't return pointers to mutable internal state
- Append to slices creates new backing array when needed
- Explicit copying with
copy()for slices
Common Questions
Q: Isn't immutability inefficient?
A: Modern runtimes optimize immutable operations. Profile first. Immutability often prevents bugs that are more costly than minor performance differences.
Q: When should I use mutation?
A: When building large structures in tight loops, implementing performance-critical algorithms, or when mutation is clearly scoped and doesn't escape the function.
Q: How do I update nested immutable structures?
A: Use spreading at each level, or use libraries like Immer (JS), lenses (functional languages), or copy-on-write helpers. Keep nesting shallow when possible.
Q: What about builder patterns?
A: Builders are fineโthey use mutation internally but expose an immutable interface. The builder itself is transient and local.
References
- "Effective Java" (Joshua Bloch) - Item 17: Minimize Mutability
- Redux documentation on immutability
- "Clojure for the Brave and True" - Immutable Data Structures
- Rich Hickey's talks on "The Value of Values"
# 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.