← All shorts
24Compose compiler 1.4+2 min read

Stability, @Immutable/@Stable, and Skippable Composables

The Compose compiler only skips recomposition when it can prove a composable's parameters are stable.

Compose skips recomposing a composable when its inputs compare equal to last time — but only if the compiler can prove those inputs are stable.

Get the types wrong and the skipping silently never happens.

#The old way

data class UiState(
    val title: String,
    val items: List<String>
)

@Composable
fun ItemList(state: UiState) {
    Column {
        state.items.forEach { Text(it) }
    }
}

List is an interface. The compiler has no way to guarantee that whatever implementation shows up behind it is immutable, so UiState is inferred unstable.

Which means ItemList can never be skipped. It recomposes whenever its parent does, regardless of whether state actually changed.

#The new way

import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

@Immutable
data class UiState(
    val title: String,
    val items: ImmutableList<String> = persistentListOf()
)

@Composable
fun ItemList(state: UiState) {
    Column {
        state.items.forEach { Text(it) }
    }
}

#Why it matters

  • A stable, skippable ItemList recomposes when state actually changes by structural equality — not just because its parent recomposed.
  • @Immutable is a hard compiler promise: no property ever changes after construction. @Stable is the softer version, which allows mutation as long as reads are observable to Compose.
  • kotlinx.collections.immutable types are stable by construction. List, Map and Set are not — the compiler always treats them as unstable.

#Gotcha

@Immutable isn't enforced at runtime. It's a promise, and you can break it by mutating a var inside the annotated class. Break it and the compiler will happily skip recomposition on data that genuinely changed, leaving stale UI on screen with no crash to point at. Verify what the compiler actually inferred with the Compose compiler's stability reports rather than trusting the annotation alone.

androidcomposeperformance

Related shorts