Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
Fixes and improvements from exhaustive audit:

Consistency fixes in SKILL.md:
- 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected)
- 'Singleton for factory instance' → clarified to 'register factory as
  singleton-scoped via DI container' (less misleading wording)
- Documentation Format section updated with note that SKILL.md itself is the
  authoritative source for related-pattern combinations

Coverage fix — Related Patterns sections:
- Added '## Related Patterns' to ALL 94 pattern files (was 0/94)
- Each section lists 3–6 related patterns with relationship descriptions
- Covers: why they're related, when to prefer one vs the other,
  and which are often confused

SOLID principles → Creational → Structural → Behavioral → Architectural →
Concurrency → Functional → Resilience → Data Access → Messaging →
Testing → Error Handling → Microservice — all 13 categories covered

Code verification:
- Python: 0 failures (all 85 testable blocks pass)
- Go: 0 failures (all 76 testable blocks pass)
- JavaScript: 0 failures (all 78 testable blocks pass)
- All 239 code blocks verified correct after edits

Final skill state:
- 108 files, 36,524 lines across 13 reference categories
- 94/94 pattern files have Related Patterns sections
- 2,815-line SKILL.md with 67 decision trees, 23 scenarios,
  0 broken references, 0 naming inconsistencies
2026-04-15 13:22:13 -04:00

6.8 KiB

Immutability Pattern

Problem

Mutable objects can be changed by any code that holds a reference, making it hard to reason about state, reproduce bugs, or safely share data between threads. Unintended mutations are a major source of bugs.

Solution

Make objects immutable after creation. Any "modification" returns a new object with the change applied, leaving the original untouched. This guarantees that once created, an object's state never changes.

When to Use

  • Configuration objects passed to many components
  • Value objects (money, coordinates, dates)
  • Data shared across threads without locks
  • State in functional pipelines
  • When you need reliable hash keys or cache keys

When to Avoid

  • High-frequency updates on large data structures (copy cost too high without structural sharing)
  • Performance-critical inner loops where allocation pressure matters
  • When the language/runtime has poor support for immutable structures

Pseudocode

@immutable
class Point:
    x: int
    y: int

    function move(dx, dy):
        return new Point(x + dx, y + dy)   // returns new, self unchanged

p1 = Point(1, 2)
p2 = p1.move(3, 4)     // p2 = Point(4, 6)
print(p1)              // Point(1, 2) -- unchanged
print(p2)              // Point(4, 6)

Python

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Point:
    x: float
    y: float

    def move(self, dx: float, dy: float) -> "Point":
        return replace(self, x=self.x + dx, y=self.y + dy)

@dataclass(frozen=True)
class User:
    name: str
    email: str
    age: int

    def with_email(self, new_email: str) -> "User":
        return replace(self, email=new_email)

    def with_age(self, new_age: int) -> "User":
        return replace(self, age=new_age)

def main() -> None:
    # Immutable Point
    p1 = Point(1.0, 2.0)
    p2 = p1.move(3.0, 4.0)
    print(f"p1 = {p1}")  # unchanged
    print(f"p2 = {p2}")  # new object

    # Try mutation -- will raise
    try:
        p1.x = 99
    except AttributeError as e:
        print(f"Mutation blocked: {e}")

    # Immutable User
    user1 = User("Alice", "alice@old.com", 30)
    user2 = user1.with_email("alice@new.com")
    user3 = user2.with_age(31)

    print(f"\nuser1 = {user1}")
    print(f"user2 = {user2}")
    print(f"user3 = {user3}")

    # Hashable (can be used as dict key / in sets)
    point_set = {Point(0, 0), Point(1, 1), Point(0, 0)}
    print(f"\nPoint set (deduped): {point_set}")

    # Immutable collection via tuple
    config = (("host", "localhost"), ("port", 8080))
    print(f"Config: {dict(config)}")

if __name__ == "__main__":
    main()

Go

package main

import "fmt"

// Point is immutable: fields are unexported, only accessible via methods
type Point struct {
	x, y float64
}

func NewPoint(x, y float64) Point {
	return Point{x: x, y: y}
}

func (p Point) X() float64 { return p.x }
func (p Point) Y() float64 { return p.y }

// Move returns a new Point (original is unchanged)
func (p Point) Move(dx, dy float64) Point {
	return Point{x: p.x + dx, y: p.y + dy}
}

func (p Point) String() string {
	return fmt.Sprintf("Point(%.1f, %.1f)", p.x, p.y)
}

// User is immutable
type User struct {
	name  string
	email string
	age   int
}

func NewUser(name, email string, age int) User {
	return User{name: name, email: email, age: age}
}

func (u User) Name() string  { return u.name }
func (u User) Email() string { return u.email }
func (u User) Age() int      { return u.age }

func (u User) WithEmail(email string) User {
	return User{name: u.name, email: email, age: u.age}
}

func (u User) WithAge(age int) User {
	return User{name: u.name, email: u.email, age: age}
}

func (u User) String() string {
	return fmt.Sprintf("User(%s, %s, %d)", u.name, u.email, u.age)
}

func main() {
	// Immutable Point
	p1 := NewPoint(1.0, 2.0)
	p2 := p1.Move(3.0, 4.0)
	fmt.Printf("p1 = %s\n", p1) // unchanged
	fmt.Printf("p2 = %s\n", p2) // new value

	// Immutable User
	user1 := NewUser("Alice", "alice@old.com", 30)
	user2 := user1.WithEmail("alice@new.com")
	user3 := user2.WithAge(31)

	fmt.Printf("\nuser1 = %s\n", user1)
	fmt.Printf("user2 = %s\n", user2)
	fmt.Printf("user3 = %s\n", user3)

	// Points as map keys (structs are value types in Go)
	pointSet := map[Point]bool{
		NewPoint(0, 0): true,
		NewPoint(1, 1): true,
		NewPoint(0, 0): true, // duplicate
	}
	fmt.Printf("\nPoint set (deduped): %d unique points\n", len(pointSet))
}

JavaScript

// Immutable Point using Object.freeze
function createPoint(x, y) {
  return Object.freeze({
    x,
    y,
    move(dx, dy) {
      return createPoint(x + dx, y + dy);
    },
    toString() {
      return `Point(${x}, ${y})`;
    },
  });
}

// Immutable User using Object.freeze
function createUser(name, email, age) {
  return Object.freeze({
    name,
    email,
    age,
    withEmail(newEmail) {
      return createUser(name, newEmail, age);
    },
    withAge(newAge) {
      return createUser(name, email, newAge);
    },
    toString() {
      return `User(${name}, ${email}, ${age})`;
    },
  });
}

function main() {
  // Immutable Point
  const p1 = createPoint(1.0, 2.0);
  const p2 = p1.move(3.0, 4.0);
  console.log(`p1 = ${p1}`); // unchanged
  console.log(`p2 = ${p2}`); // new object

  // Try mutation -- silently fails in non-strict, throws in strict mode
  try {
    "use strict";
    const p = createPoint(0, 0);
    p.x = 99;
  } catch (e) {
    console.log(`Mutation blocked: ${e.message}`);
  }

  // Immutable User
  const user1 = createUser("Alice", "alice@old.com", 30);
  const user2 = user1.withEmail("alice@new.com");
  const user3 = user2.withAge(31);

  console.log(`\nuser1 = ${user1}`);
  console.log(`user2 = ${user2}`);
  console.log(`user3 = ${user3}`);

  // Deep freeze utility
  function deepFreeze(obj) {
    Object.getOwnPropertyNames(obj).forEach((name) => {
      const value = obj[name];
      if (typeof value === "object" && value !== null) {
        deepFreeze(value);
      }
    });
    return Object.freeze(obj);
  }

  const config = deepFreeze({
    server: { host: "localhost", port: 8080 },
    db: { url: "postgres://localhost/mydb" },
  });
  console.log(`\nConfig: ${JSON.stringify(config)}`);

  try {
    config.server.port = 9999;
  } catch (e) {
    console.log(`Deep mutation blocked: ${e.message}`);
  }
  console.log(`Port still: ${config.server.port}`);
}

main();
  • Value Object — value objects are immutable by definition; immutability is a core property of the Value Object pattern.
  • Flyweight — flyweight shares immutable objects to reduce memory; immutability makes sharing safe.
  • Prototype — copy-on-write (creating a new copy before modifying) is a common immutability strategy.
  • Actor Model — actors pass immutable messages to avoid shared mutable state across concurrent units.