Lesson 4

SubcomposeLayout turns slots into a reuse pool

Read the active, reusable, and precomposed child ranges, then follow paused precomposition as a handle workflow.

4 min readUpdated Jul 12, 2026

Evidence legend: Durable marks a stable mental model; Version-specific marks behavior tied to the AndroidX pin below; Experimental marks a changing or transitional path.

SubcomposeLayoutState is not just a map from IDs to lambdas. At the pinned source, LayoutNodeSubcompositionsState keeps virtual child LayoutNodes and a NodeState for each composition. Its child list has exactly three storage ranges: active children, a reusable middle section, and precomposed children waiting at the end (SubcomposeLayout.kt#L506-L568). Paused is not a fourth range; it describes an incremental precomposition workflow/handle whose slot is still represented in the precomposed range. This is the storage model behind lazy-layout prefetch and slot reuse.

Active, reusable, and precomposed

During a measure pass, subcompose(slotId, content) first looks in slotIdToNode. If the ID is not active, it takes a matching node from precomposeMap, then tries takeNodeFromReusables, and only then creates a new node. It moves the chosen node to the current active index and composes it if the content changed, the composition has invalidations, or reuse forced a fresh pass (SubcomposeLayout.kt#L583-L624, SubcomposeLayout.kt#L635-L719). The slot ID chooses the child slot; it does not by itself preserve every remembered value when content is reused.

When a pass ends, disposeOrReuseStartingFromIndex constructs the available slot-ID set and asks SubcomposeSlotReusePolicy which IDs to retain. Retained nodes enter the reusable section and their compositions are reset for reuse; unretained nodes are disposed. When a future ID asks for a node, the implementation first searches for an exact previous ID, then asks areCompatible(newId, reusableId) for a compatible candidate (SubcomposeLayout.kt#L734-L828, SubcomposeLayout.kt#L871-L923). A policy is therefore a bounded reuse decision, not a promise to keep all off-screen content alive.

The pinned tests expose the controls: slotsKeptForReuse keeps two old slots and asserts deactivation; newSlotIsUsingReusedSlot adds an ID and checks that reuse is consumed; nothingIsReusedWhenMaxSlotsAre0 confirms zero retention disposes old slots (SubcomposeLayoutTest.kt#L892-L924, SubcomposeLayoutTest.kt#L1050-L1088). The bounded-retention evidence here is UI-specific: the Compose UI release notes describe SubcomposeLayoutState retaining slots and lazy rows/columns keeping previously visible items for reuse (Compose UI 1.0.0-beta07). The pinned UI source and tests define the current ranges and policy behavior. Compose Runtime 1.6.0-alpha03 notes are reusable-node context, not evidence for this SubcomposeLayout pool.

precompose(slotId, content) moves a reusable node to the precomposed range or creates one, then composes it before a later measure call. The returned PrecomposedSlotHandle can dispose the still-precomposed slot or premeasure its placeables; if regular subcompose has already claimed the slot, dispose does nothing (SubcomposeLayout.kt#L1014-L1040, SubcomposeLayout.kt#L328-L369). The precompose test verifies composition happens before the slot is needed during measure; disposePrecomposedItem verifies the handle ends its effect (SubcomposeLayoutTest.kt#L660-L723).

Paused precomposition: a pinned, narrow path

Version-specific The pinned source exposes createPausedPrecomposition. Its handle can resume(shouldPause), report isComplete, then apply or cancel; the API describes incremental work between frames and the implementation routes it through precomposePaused (SubcomposeLayout.kt#L246-L326, SubcomposeLayout.kt#L1185-L1210). The pinned precomposePaused_composeAndApply, precomposePaused_isComplete, and precomposePaused_applyOnNotCompletedPrecompositionThrows tests prove resume/complete/apply sequencing (SubcomposeLayoutTest.kt#L3276-L3470). Keep this claim scoped to the pinned API and tests, not every prefetcher.

Reproduce the boundary

At the pinned AndroidX checkout with an attached device or emulator, run:

./gradlew :compose:ui:ui:connectedAndroidTest \
  -Pandroidx.testInstrumentationRunnerArguments.class=androidx.compose.ui.layout.SubcomposeLayoutTest

Controls: compare zero/bounded retention, consume a precomposed slot during measure, and call apply before and after completion. Expected: retained nodes are deactivated rather than immediately disposed, precomposition removes later composition work, and incomplete paused work rejects apply. Limits: device timing and platform setup are required; this does not prove a particular lazy list’s policy. This procedure was not executed in this content edit.

Check yourself

Given active A, B, reusable C, D, and precomposed E, a request for E claims the precomposed node; a compatible new ID searches reusable; zero retention disposes. A precomposed handle may be paused, but that workflow state is not another range and is not applicable merely because resume was called: check completion before apply.

Evidence ledger

ClaimEvidenceLabel
Child nodes are partitioned into active, reusable, and precomposed rangesSubcomposeLayout.kt#L535-L568Version-specific
Policy and compatibility decide reuse versus disposalSubcomposeLayout.kt#L734-L923Version-specific
Paused precomposition requires completion before applySubcomposeLayout.kt#L246-L326, SubcomposeLayoutTest.kt#L3441-L3470Version-specific

Misconceptions

  • “A slot ID is a slot-table address.” It matches child slots within one SubcomposeLayoutState; it is not a global composition identity.
  • “Precomposition immediately inserts visible nodes.” It composes a slot ahead of demand; ordinary subcompose still claims it for the active range.
  • “Calling resume makes paused work applyable.” The handle must report completion before apply; incomplete work follows the pinned precondition.

Freshness

Refresh when active/reusable/precomposed range management, slot-reuse policy, precomposition handles, or paused-precomposition completion rules change. Keep the paused path scoped to the pinned SubcomposeLayoutState implementation and tests.

Finished this lesson?

Your progress stays only in this browser.