JavaScript and TypeScript make it dangerously easy to treat object references as disposable. You create an object, pass it to a function, store it in a cache, and later replace the variable with a fresh instance. The language does not complain. The old reference still exists somewhere else in your program, pointing to data that is no longer current. This is not a crash. It is something worse: a silent divergence between two parts of your codebase that both think they own the truth.

The discipline that prevents this is called Hard Object References. It is not a library or a compiler feature. It is a contract you enforce across your code.

The Stale Alias Problem

A stale alias happens when one module holds a reference to an object while another module replaces that object with a new one. The first reference is still valid code, but it no longer points to current data.

Picture a user record in a typical web application:

const user = {
  name: "Alice",
  address: {
    city: "Seoul",
    country: "KR"
  }
};

A shipping component captures the address early:

const shippingAddress = user.address;

Later, a profile update arrives. A reducer or service handler decides to replace the entire object:

user.address = { city: "Tokyo", country: "JP" };

At this point, user.address points to Tokyo. But shippingAddress still points to the old object in Seoul. No exception is thrown. TypeScript is happy because the types still match. The UI might show the updated city on the profile page while the shipping label quietly prints the old one. The bug only surfaces when a user complains that their package went to the wrong country.

This happens because JavaScript separates identity from value. When you replace an object property with a new object literal, you break the chain. The old object is not destroyed; it is merely orphaned. Anyone still holding it is working with a ghost.

What Hard Object References Mean

The rule is simple: replace primitive values, but never replace object or array references. When new data arrives, you copy it into the existing container instead of swapping the container out.

This requires three concrete habits.

First, declare objects and arrays with const. This removes the temptation to rebind the top-level variable to a new instance. The variable should remain fixed for the lifetime of the scope.

Second, never replace a property that holds an object or array with a freshly created one. If you need to update an address, mutate the properties inside it.

Third, if you need to clear or reset state, empty the existing structure rather than discarding it for a new empty object or array.

Revisiting the address example, the correct update looks like this:

user.address.city = "Tokyo";
user.address.country = "JP";

If the incoming data is partial or dynamic, use Object.assign to write into the existing target:

Object.assign(user.address, incomingAddressData);

The shippingAddress variable, which points to the exact same object in memory, now sees the new fields immediately. There is only one canonical object serving as the live source of truth.

Where This Matters Most

The discipline seems like overkill for a flat configuration object. It becomes essential once your state grows into a graph where multiple subsystems hold pointers to overlapping nodes.

Consider a rich text editor. The document model is a tree of nodes. A selection model holds references to the start and end nodes. The history buffer holds references to nodes that changed in the last operation. The rendering layer holds references to nodes it measured for layout. If the state manager replaces a paragraph node with a new object because its text changed, every one of those subsystems is now holding a stale alias. The selection highlights the wrong region. The history system cannot revert correctly. The renderer crashes or, worse, displays phantom cursors.

The same risk appears in user profiles with nested settings and permissions referenced by the UI, the access-control layer, and the autosave routine. It appears in layout engines where parent containers cache measurements of child nodes. It appears in visual editors and canvas tools where a runtime controller tracks active entities by reference. In all of these domains, components grab a handle to an object and expect that handle to remain a live view of the truth.

Hard Object References treat the object as a stable address. The furniture inside can change, but the door stays in the same place. Anyone holding the address can walk in and see the current layout.

Reactivity Instead of Replacement

Якщо ви працювали з Redux або подібними бібліотеками незмінних (immutable) станів, ця модель, ймовірно, здасться вам парадоксальною. У таких системах про зміну сигналізує створення нового об'єкта. Зміна посилання і є сигналом. Компоненти порівнюють prevProps.data === nextProps.data, щоб знати, чи потрібно робити рендеринг.

Hard Object References вимагають від вас змінити це припущення. Оскільки посилання залишається незмінним, порівняння посилань нічого не говорить про те, чи змінилися дані. Вам потрібен інший спосіб сповіщення про оновлення.

На практиці це означає покладання на системи реактивності, явні спостерігачі (observers) або прапорці (dirty flags). Мутація user.address.city може активувати сеттер, який сповіщає підписників. Об'єкт може надсилати подію зміни через шину подій (event bus). Ігровий цикл або інструмент для роботи з canvas може встановлювати глобальний dirty flag і пересканувати граф наприкінці кадру. Посилання є стабільним, тому ви повинні зробити потік даних видимим за допомогою інших механізмів.

Цей архітектурний зсув — причина, чому такий підхід найкраще підходить для складного стану фронтенду, великого локального стану компонентів, візуальних редакторів, інструментів canvas та контролерів виконання (runtime controllers). Ці системи вже покладаються на гранулярні оновлення, прямі мутації або імперативні API. Нав'язування незмінності (immutability) зверху часто створює надмірне навантаження на виділення пам'яті (allocation pressure) та постійну зміну посилань (reference churn), не даючи при цьому пропорційної переваги у чіткості. Коли важливий кожен кадр, виділення нового графа об'єктів лише для того, щоб пересунути повзунок, є марнотратством. Збереження жорсткого посилання та мутація внутрішнього вмісту відповідають реальній механіці проблеми.

Як закріпити результат

Однією з недооцінених переваг цього правила є