Skip to content

Change Reference to Value (and back)

A small object can be modelled two ways. As a reference, one shared instance is held by many owners; updating it updates everyone’s view, and identity (which object) matters. As a value, the object is immutable, equal to any other with the same contents, and copied freely. Change Reference to Value turns a shared, mutable object into an immutable value compared by its fields. The reverse — Change Value to Reference — applies when many places really must share and observe a single, updatable entity.

The reference-when-it-should-be-value smell shows up as aliasing bugs: two parts of the program hold the same little object, one mutates it, and the other is surprised by the change it never asked for. A Money, a Coordinate, a DateRange — these have no meaningful identity, so sharing them only invites action at a distance. The opposite smell is divergent copies: you have many separate instances of something that is conceptually one entity (a single Customer record), and an update to one copy fails to reach the others.

A Money object shared by reference, where mutating it leaks into every alias. After, it is an immutable value: operations return new instances and equality compares contents.

// Before — mutable, shared by reference
class Money {
constructor(public amount: number, public currency: string) {}
add(other: Money): void {
this.amount += other.amount; // mutates in place, surprising aliases
}
}
// After — immutable value: operations return new instances
class Money {
constructor(
readonly amount: number,
readonly currency: string,
) {}
add(other: Money): Money {
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}

To change a reference into a value:

  1. Make the object’s fields immutable — readonly, frozen, value receivers, no &mut.
  2. Replace any mutating method with one that returns a new instance carrying the changed contents.
  3. Provide content-based equality (equals, __eq__/frozen, == on a comparable struct, PartialEq), so two objects with the same fields are equal.
  4. Remove sharing: let each owner hold its own copy, since copies are now indistinguishable.
  5. Run your tests after each step.

To change a value into a reference (the reverse), introduce a single source — a repository or registry that hands out the instance for a given identity — so every caller fetches the same object and an update is seen by all.

Prefer a value for small, self-contained concepts — money, dates, points, ranges — where two equal contents should be interchangeable and nobody needs to observe in-place changes. Immutable values are safe to share across threads, easy to reason about, and free of aliasing surprises; their cost is allocating a new instance per “change,” which is negligible for small objects.

Choose a reference when the object models a single real-world entity that several parts of the system must share and watch as it updates — a logged-in user, an account whose balance many views reflect. Here, divergent copies would be a correctness bug. The decisive question: if I change this object, should everyone holding it see the change? Yes means reference; no — and equal-contents-are-the-same — means value.

What characterises a value object as opposed to a reference?
Which bug typically signals that a shared reference should become a value?
When you turn a mutable reference into a value, what replaces an in-place mutating method?
What single question best decides between reference and value?