Back to Blog
Android • Jetpack Compose

Stop Over-Recomposing Everything in Jetpack Compose

One of the biggest mistakes developers make when optimizing Jetpack Compose is thinking in terms of recalculating everything instead of observing changes.

Compose is already fast — if you let it do its job. Performance issues usually appear when we accidentally tell Compose to redo work even though nothing meaningful changed.

1. Filtering a List on Every Recomposition

The bad way:


@Composable
fun ContactList(
    contacts: List<Contact>,
    searchQuery: String
) {
    val filteredContacts =
        contacts.filter { it.name.contains(searchQuery) }

    LazyColumn {
        items(filteredContacts) {
            ContactRow(it)
        }
    }
}
      

A composable can recompose for many reasons: animations, timers, parent recompositions, or unrelated state changes. Even if searchQuery didn’t change, the filter runs again.

The correct approach:


@Composable
fun ContactList(
    contacts: List<Contact>,
    searchQuery: String
) {
    val filteredContacts = remember(
        contacts,
        searchQuery
    ) {
        contacts.filter {
            it.name.contains(searchQuery)
        }
    }

    LazyColumn {
        items(filteredContacts) {
            ContactRow(it)
        }
    }
}
      
remember caches the result, not the computation. Recomposition becomes cheap and predictable.

2. Reacting to Noisy Scroll State


@Composable
fun ScrollButton(listState: LazyListState) {
    val showButton =
        listState.firstVisibleItemIndex > 5

    if (showButton) {
        FloatingActionButton(onClick = { })
    }
}
      

Scroll state changes constantly. You are reacting to noise, not meaningful state changes.


@Composable
fun ScrollButton(listState: LazyListState) {
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 5
        }
    }

    if (showButton) {
        FloatingActionButton(onClick = { })
    }
}
      

3. Unstable Data Classes


data class UserState(
    val name: String,
    val tags: List<String>
)
      

Because List is mutable by nature, Compose cannot safely skip recomposition.


@Immutable
data class UserState(
    val name: String,
    val tags: ImmutableList<String>
)
      
@Immutable is a promise to the Compose compiler: if the reference is the same, the data is the same.

Final Thoughts

Jetpack Compose performance is not about avoiding recomposition. It’s about reacting only to meaningful state changes. Let Compose do what it was designed to do.

Select Theme