Lesson 1

A state object is a record chain

Explain how one snapshot-aware state object stores versioned records and selects a readable value.

4 min readUpdated Jul 12, 2026

Status: A state object owning versioned records is Durable . The linked-list fields, snapshot-ID algorithm, and record-reuse helpers are Version-specific at the pinned revision.

Outcome

Given a MutableState, you should be able to explain why the same object can return different values in different snapshots without copying every object it references.

The object owns versions, not a copied object graph

StateObject is the snapshot module’s interface for an object with state records. It exposes the first record in a linked list and a method for prepending a record. A record is a StateRecord with a snapshot ID, a next link, and copy/assignment operations (Snapshot.kt).

A mutableStateOf("ready") therefore does not mean “make a snapshot of the whole model.” The state object keeps records for the values that snapshots may need. The snapshot chooses which record is visible. The record chain is local to that state object; it is not a second copy of an application tree.

Diagram
flowchart LR
    State[StateObject] --> Newest[Newest record]
    Newest --> Older[Older record]
    Older --> Base[Base record]
    Snapshot[Current snapshot] --> Select[Select valid record]
    Newest --> Select
    Older --> Select
    Base --> Select
    Select --> Value[Visible value]

At this revision, SnapshotMutableStateImpl stores a StateStateRecord chain. Its value getter calls readable, and its setter uses overwritable after the policy says the value is not equivalent (SnapshotState.kt). The concrete class is private to the runtime, so treat its name as Version-specific , not as an application extension point.

Readability is a snapshot question

The pinned readable helper walks the chain and keeps the valid record with the greatest snapshot ID. A candidate is valid when its ID is not reserved, is no later than the current snapshot, and is not in that snapshot’s invalid-ID set (Snapshot.kt). The winner is therefore not simply “the first record” or “the newest value globally.” It is the newest record visible to this snapshot.

readable(state) also invokes the current snapshot’s read observer before selecting a record. That callback is one of the ways an observed read can become an edge in a larger system; Lesson 5, “Reads become observation edges,” follows the composition and observer maps. The read itself still answers only a consistency question: which record is visible here?

A write gets a record for its snapshot

The writable helper first finds the record readable by the current snapshot. If that record was born in the current snapshot, it can be changed. Otherwise, the runtime copies the readable data into a reusable or new record, gives that record the current snapshot ID, and prepends it to the state object. After the block, notifyWrite advances the snapshot write count and invokes its write observer (Snapshot.kt).

This is why a write inside an isolated mutable snapshot can be immediately visible to that snapshot while remaining invisible to the global snapshot. The value lives in a record that the global snapshot does not select yet. The record chain is the mechanism; no global lock is the mental model.

Reproduce the bounded behavior

This is a pinned upstream-test procedure, not an experiment run for this lesson. In a checkout of AndroidX at cc1caf65677fc10a8ce8116eba46e716f4cef222, run:

./gradlew :compose:runtime:runtime:desktopTest \
  --tests 'androidx.compose.runtime.snapshots.SnapshotTests.testRecordsAreReusedCorrectly' \
  --tests 'androidx.compose.runtime.snapshots.SnapshotTests.testSnapshotStateIsBornAccessible'

Use SnapshotTests.kt as the exact control and mutation fixture. The first test keeps a read-only snapshot at value 1 while later global writes advance the state; the second checks that a state created in a mutable snapshot exposes its initial value before the snapshot applies, but its later modification only after apply. Control: remove the later write or read outside readable1; the retained snapshot should still see its own version. Interpretation limit: these assertions demonstrate visibility and record retention. They do not expose private chain order, prove a performance property, or cover every platform’s synchronization implementation.

Misconceptions

  • “A snapshot copies the whole object graph.” Snapshot records are attached to individual state objects.
  • “The first record is always the current value.” The current snapshot selects the highest valid record.
  • “A write changes the global value immediately.” It first changes a record visible to the active mutable snapshot.

Check yourself

Two records have IDs 8 and 11. The current snapshot is 12, but ID 11 is invalid for it. Which record is readable, and why is “highest ID wins” incomplete?

Source notes

ClaimDirect evidenceStatus
StateRecord is a linked record with snapshot identitySnapshot.ktDurable Version-specific
Readability selects the greatest valid record for a snapshotSnapshot.ktVersion-specific
mutableStateOf reads and writes through snapshot recordsSnapshotState.ktVersion-specific
Upstream tests bound record retention and birth visibilitySnapshotTests.ktVersion-specific

Freshness

Refresh this lesson when StateRecord, readable, writable, snapshot-ID validity, or record reclamation changes. The pinned history includes a recent deprecation-message change for StateRecord.withCurrent (5326fc2); that is a reminder that helper names and compatibility surfaces can move even when the record-chain model remains useful.

Finished this lesson?

Your progress stays only in this browser.