# 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

```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

```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

```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();
```

## Related Patterns

- **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.
