Reducing Recomposition in Jetpack Compose
Reducing Recomposition in Jetpack Compose
Jetpack Compose recomposes only the composable functions whose inputs have changed, but this optimization depends on the compiler's ability to determine whether inputs are stable. When the compiler cannot prove stability, it marks parameters as unstable and recomposes the function on every parent recomposition regardless of whether the actual values changed. Understanding how the Compose compiler evaluates stability and what techniques reduce unnecessary recomposition is essential for building performant UIs. By the end of this lesson, you will be able to:
- Explain how the Compose compiler determines parameter stability and its effect on skipping.
- Apply
@Immutableand@Stableannotations correctly to domain classes. - Use immutable collections to prevent stability inference failures.
- Describe how lambda capturing behavior affects recomposition.
- Configure the stability configuration file and strong skipping mode.
Stability and the Skipping Mechanism
The Compose compiler analyzes every composable function and every parameter type at compile time. A composable function is skippable when the compiler can prove that all of its parameters are stable. A type is considered stable when it satisfies two conditions: its equals() result is consistent (calling equals() on the same two instances always returns the same value), and any changes to public properties are observable by the Compose runtime through snapshot state.
Primitive types, String, and function types are inherently stable. Data classes where all constructor properties are themselves stable types are also inferred as stable. However, standard collection types like List, Set, and Map are not stable because the compiler cannot distinguish between mutable and immutable implementations at the call site:
// Unstable: List could be a MutableList at runtime
data class UserList(val users: List<User>)
// Stable: ImmutableList guarantees no mutation
data class UserList(
val users: ImmutableList<User>
)
When a parameter is unstable, the composable function is not skippable. The runtime calls the function body on every parent recomposition even if the parameter instance has not changed. This is the single largest source of unnecessary recomposition in most Compose applications.
@Immutable and @Stable Annotations
The @Immutable annotation tells the compiler that all public properties of a class will never change after construction. The @Stable annotation is slightly broader and states that if properties do change, those changes will be notified to the Compose runtime through the snapshot system (for example, properties backed by mutableStateOf).
At the code generation level, both annotations produce the same effect: the compiler treats the annotated type as stable and enables skipping for composable functions that receive it. The distinction is semantic. Use @Immutable when the object is truly immutable:
This interview continues for subscribers
Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.
Become a Sponsor