This episode optimizes Compose: minimizing unnecessary recomposition, proper use of remember and derivedStateOf, stable types for skipping, and UI performance benchmarking and debugging.

Reactive applications are good, but smooth applications are better. Episode 15 discusses why some screens feel heavy — often because of unnecessary recomposition — and how to fix it systematically.
Compose already optimizes a lot automatically, but that behavior can be fought: state read too high up, unstable objects, and lists without keys make Compose work harder than it should.
Episode 15 covers minimizing recomposition, remember and derivedStateOf, stable types, and measurement and debugging.
The golden principle: hoist state, lower the readers. A composable that reads state will be recomposed when that state changes. The higher the reader sits, the more of the subtree is re-run.
@Composable
fun HalamanProfil() {
// jangan baca state nama di sini
Header()
BodyNama() // baca di sini
}
@Composable
fun BodyNama() {
var nama by remember { mutableStateOf("") }
Text(nama)
}If nama is read in HalamanProfil, the whole page recomposes every time a character changes. By reading in BodyNama, only that part gets updated.
Don't create new state objects inside frequently recomposed composables without remember. Without remember, the value is reset on every composition — which also makes derived state useless.
derivedStateOf from episode 5 becomes a primary performance tool: it computes a derived value only when needed and skips recomposition if the result is the same.
val listItem by remember { mutableStateOf(allItems) }
val tampilJumlah by remember {
derivedStateOf { listItem.filter { it.aktif }.size }
}
Text("Aktif: $tampilJumlah")derivedStateOf computes the active count only when listItem changes, not every frame. Without derivedStateOf, the filter re-runs more often than necessary.
For expensive, stable calculations, store the result with remember:
val teksTerformat by remember(data) {
mutableStateOf(formatData(data))
}remember(data) stores the formatData result and recalculates only when data changes.
Compose skips recomposing a composable if all its parameters are stable and unchanged. Unstable objects — for example classes without @Immutable or @Stable — make Compose always suspect changes:
@Immutable
data class KartuInfo(val judul: String, val nilai: Int)@Immutable tells Compose an instance will never change, so parameters of type KartuInfo can be skipped. @Stable is used for objects that change through the snapshot mechanism — like classes containing mutableStateOf inside.
In episode 9 you used key. The same principle protects performance: without a key, Compose treats every item as changed. Add stable key and contentType for large lists.
Layout Inspector in Android Studio shows which composables are recomposed: click the Recomposition Counts button to see the composition count per node. A quick reading pattern:
Text(
text = "Beranda",
modifier = Modifier.layoutId("judul_beranda")
)Modifier.layoutId("judul_beranda") gives a label so it's easy to track in the Layout Inspector. This tool is used alongside Compose Preview and other tooling that will be discussed in episode 21.
Baseline Profiles speed up startup and critical interactions by compiling ahead of time. Add the profileinstaller library:
implementation("androidx.profileinstaller:profileinstaller:1.4.1")The androidx.profileinstaller:profileinstaller library enables applying the baseline profile at startup. Profiles are generated from production traces and can significantly improve first-run performance. To measure the impact, compare startup times with adb shell am start -W before and after the profile is applied.
Episode 15 optimized Compose: reading state as low as possible, using remember and derivedStateOf in the right places, stabilizing parameter types with @Immutable and @Stable, using keys in lazy lists, and debugging with Layout Inspector and baseline profiles.
Key takeaways:
In episode 16 we will discuss interop and migration — embedding Compose into XML applications, using AndroidView and ComposeView, migrating screens gradually, and hybrid architecture patterns.