derivedStateOf: Avoid Unnecessary Recompositions

Quick answer: use derivedStateOf only when state changes often but the UI result changes less often—for example, a scroll position becoming a Boolean such as “show scroll-to-top.” Wrap it in remember. Do not use it for ordinary values, such as "$firstName $lastName", that must update every time their inputs change.

derivedStateOf creates observable Compose state whose value updates only when the calculated result differs from its previous result. It resembles distinctUntilChanged() for a calculated UI value, but it has a cost, so it is not a default replacement for normal Kotlin expressions.

The official side-effects guide explicitly cautions that derivedStateOf is expensive and should only prevent recompositions when its result has not changed.

The decision rule

Input behaviorUI result behaviorUse derivedStateOf?
Changes oftenChanges only at thresholdsYes
Changes once and result must change onceChanges equally oftenNo
Derived value is needed only in one composition passNo observable state neededNo; calculate it directly
Need a Flow for analytics or a suspend operationEvent stream neededUse snapshotFlow, not derivedStateOf alone

The important mismatch is input frequency versus result frequency. A list index can change 0, 1, 2, 3, and 4 during a scroll, while a button visibility result may remain true through all of them.

The scroll-threshold pattern

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember

@Composable
fun ArticleList() {
    val listState = rememberLazyListState()
    val showScrollToTop by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 0 }
    }

    // LazyColumn(state = listState) { ... }

    AnimatedVisibility(visible = showScrollToTop) {
        ScrollToTopButton()
    }
}

firstVisibleItemIndex changes while the user scrolls. The button only needs to change when the result crosses falsetrue, so the derived Boolean avoids invalidating readers for every index change. This is the same use case shown in the Android documentation.

Keep the derived state inside remember. Recreating it on each recomposition defeats the purpose and changes the state object that its readers observe.

When direct calculation is correct

This code adds overhead without reducing updates:

// Do not do this.
val fullName by remember { derivedStateOf { "$firstName $lastName" } }

The full name must change whenever either name changes, so a direct expression is clearer and correct:

val fullName = "$firstName $lastName"

Likewise, do not introduce derivedStateOf simply because a value is derived. Derivation is normal Kotlin; the API is specifically for filtering unchanged results from frequent state changes.

UI state is not an event stream

derivedStateOf exposes state for Compose to read. It does not run a side effect. If crossing a threshold should send analytics, trigger a load, or show a snackbar, convert snapshot reads into a snapshotFlow inside LaunchedEffect and apply Flow operators there. Compose UI phases explains why the phase where state is read affects the work Compose restarts.

Test and measure before optimizing

  • Verify that the UI result really stays unchanged through many input updates.
  • Keep the calculation small, deterministic, and free of side effects.
  • Test threshold boundaries, including scroll position zero and the first item after it.
  • Profile a real problem before adding derived state broadly; it is an optimization, not a general state-management pattern.

For scroll ownership and programmatic position changes, see rememberLazyListState. For state ownership, read State Hoisting.

A practical rule

Use derivedStateOf when frequent input changes collapse into a stable UI result. Calculate ordinary values directly, and use Flow/effect APIs for one-time work. That narrow rule keeps the API useful without adding state machinery where it cannot save recompositions.