Learn Jetpack Compose - Performance Optimization
Episode 15 of 23

Learn Jetpack Compose - Performance Optimization

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

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

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.

Minimizing Recomposition

Read State as Low as Possible

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.

KotlinBaca state di tempat terendah
@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.

Avoid New State in Expensive Composables

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.

Using remember and derivedStateOf Properly

derivedStateOf for Derived Values

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.

KotlinderivedStateOf untuk filter
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.

remember for Expensive Calculations

For expensive, stable calculations, store the result with remember:

Kotlinremember hasil mahal
val teksTerformat by remember(data) {
    mutableStateOf(formatData(data))
}

remember(data) stores the formatData result and recalculates only when data changes.

Stable Types and Skipping

Parameter Stability

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:

KotlinMenandai class stabil
@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.

Lazy Lists and Keys

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.

Benchmarking and Debugging

Layout Inspector and Recomposition Counter

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:

KotlinMenandai node
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

Baseline Profiles speed up startup and critical interactions by compiling ahead of time. Add the profileinstaller library:

KotlinBaseline profile
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.

Closing

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:

  • Read state in the lowest composable so recomposition is minimal.
  • derivedStateOf computes derived values only when needed.
  • remember stores the results of expensive calculations.
  • @Immutable and @Stable let Compose skip composables.
  • Key and contentType keep lazy list performance.
  • Layout Inspector and baseline profiles debug and speed up the UI.

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.

Learn Jetpack Compose - Performance Optimization | Learn Jetpack Compose