Immutability vs Mutability
The Questionโ
Should we always use mutable or immutable operations?
The Answer: Immutable by Defaultโ
// โ Mutable (Lodash style) - AVOID
const array = [1, 2, 3];
_.reverse(array); // Modifies the original!
console.log(array); // [3, 2, 1] ๐ฑ
// โ
Immutable (Pithos style) - PREFER
const array = [1, 2, 3];
const reversed = reverse(array); // New instance
console.log(array); // [1, 2, 3] โ
console.log(reversed); // [3, 2, 1] โ
Pithos Rulesโ
| Principle | Implementation |
|---|---|
| Immutable by default | All functions return new instances |
| No side effects | Functions never modify their arguments |
| Predictable behavior | Same input = same output (referential transparency) |
| Mutable variants (optional) | Mut suffix for rare performance-critical cases |
The Rare Exceptionsโ
For performance-critical cases where mutation is necessary:
// Immutable version (default)
export const shuffle = <T>(array: T[]): T[] => {
const result = [...array];
// ... Fisher-Yates on result
return result;
};
// Mutable version (optional, for performance)
export const shuffleMut = <T>(array: T[]): T[] => {
// ... Fisher-Yates in-place
return array; // Returns the same modified array
};
Why Immutable?โ
- Predictability: Easier to reason about and debug
- React/Vue/Solid: Modern frameworks expect immutability
- Concurrent safety: No race conditions with shared data
- Time-travel debugging: Redux DevTools, etc.
- Pure functions: Better for tests and composition
tip
Performance: With modern JS engines (V8), the cost of copying is often negligible.
Optimize only after measuring a real bottleneck.
Related
- Error Handling โ Fail fast, fail loud
- API Design โ One function, one responsibility
- Performance & Bundle Size โ Tree-shaking and loop optimization