remember vs rememberSaveable vs derivedStateOf — when do you reach for each?
All three cache a value across recomposition, but they solve different problems — surviving a rotation, surviving a process death, or avoiding wasted work.
LEARN BY DOINGPut your intuition to work3 steps
remember vs rememberSaveable vs derivedStateOf — when do you reach for each?
Use the lesson’s examples and explanation to check your idea.
Personal reflection. Nothing is sent to an AI service.#They all cache — the question is against what
All three APIs store a value so it survives something other than a plain variable would, but each one answers a different "survives what?" question. remember answers "survives recomposition of the same composition." rememberSaveable answers "survives recomposition, configuration changes, and process death." derivedStateOf doesn't answer a survival question at all — it answers "how do I stop reads of a frequently-changing input from recomposing something that doesn't actually need to."
#remember: a slot in the Composition, nothing more
remember { ... } stores its value in a slot inside the Composition's slot table, keyed by call-site position. As long as the composable stays in the composition — it isn't removed from the tree, and the whole composition isn't torn down — the value survives every recomposition, because recomposition re-runs the function body but does not rebuild the slot table from scratch. The moment the composable leaves the composition (its if branch stops being taken, its position in a list disappears, or the Activity hosting it is destroyed and rebuilt), the slot is discarded and the value is gone.
That last case is the one that trips people up: a configuration change like a screen rotation destroys and recreates the Activity by default, which tears down and rebuilds the entire Composition. A remember { mutableStateOf(0) } counter resets to 0 on rotation — not because Compose is unreliable, but because "the composition was destroyed" is explicitly one of the cases remember was never designed to survive.
#rememberSaveable: the same slot, plus a Bundle
rememberSaveable does everything remember does, and additionally serializes the value into the SavedStateRegistry — the same mechanism behind Activity.onSaveInstanceState. That gives it two extra guarantees remember doesn't have: it survives a configuration change, and it survives process death (the OS killing your app in the background under memory pressure and later restoring it), because the saved Bundle is written by the OS independently of your process staying alive.
By default it works out of the box only for types that fit in a Bundle: primitives, String, and anything Parcelable or Serializable. For anything else, you supply a Saver that knows how to flatten your type into something Bundle-compatible and rebuild it later.
data class FilterState(val query: String, val onlyFavorites: Boolean)val FilterStateSaver = Saver<FilterState, List<Any>>(save = { listOf(it.query, it.onlyFavorites) },restore = { FilterState(query = it[0] as String, onlyFavorites = it[1] as Boolean) },)@Composablefun SearchScreen() {var filter by rememberSaveable(stateSaver = FilterStateSaver) {mutableStateOf(FilterState(query = "", onlyFavorites = false))}// filter now survives rotation AND process death, using the same// Bundle-based mechanism as onSaveInstanceState.}
mapSaver and listSaver are the built-in shortcuts for this exact pattern when your type is a simple data holder — reach for a fully custom Saver only when the shape genuinely needs it.
#derivedStateOf: not about surviving anything
derivedStateOf solves an entirely different problem: a value computed from state that changes far more often than the computed result actually does. The textbook example is LazyListState.firstVisibleItemIndex, which changes on every pixel scrolled, feeding a boolean like "should the scroll-to-top button be visible" that only flips a handful of times per session. Reading firstVisibleItemIndex directly inside a composable recomposes it on every scroll tick. Wrapping the computation in derivedStateOf still re-evaluates the lambda on every tick — that part isn't free — but it only notifies readers of the derived State when the computed result itself actually changes.
@Composablefun ScrollToTopFab(listState: LazyListState, onClick: () -> Unit) {val showButton by remember {derivedStateOf { listState.firstVisibleItemIndex > 0 }}AnimatedVisibility(visible = showButton) {FloatingActionButton(onClick = onClick) {Icon(Icons.Default.ArrowUpward, contentDescription = "Scroll to top")}}}
Two things engineers reliably get wrong here. First, derivedStateOf almost always needs to be wrapped in its own remember — without it, a brand-new derivedStateOf object (with no memory of the last computed value) is created on every recomposition, which defeats the entire optimization. Second, derivedStateOf is not a general-purpose memoization tool for "anything I compute from state" — it has its own bookkeeping cost (tracking which snapshot state the lambda read, plus an equality check on every read), so for something cheap like "$firstName $lastName", a plain val computed inline during composition is simpler and just as fast. Reach for derivedStateOf specifically when the input changes much more frequently than the derived output, and that output feeds composables that would otherwise recompose needlessly often.
#Lifecycle, side by side
#Decision table
| API | Survives recomposition | Survives config change | Survives process death | Typical use |
|---|---|---|---|---|
remember | Yes | No | No | UI-only, disposable state: an expanded/collapsed flag, an animation's Animatable, a LazyListState you don't need restored |
rememberSaveable | Yes | Yes | Yes | Anything the user would be annoyed to lose: form text, a selected tab, scroll position they care about |
derivedStateOf (usually inside remember) | Yes (same as remember) | No | No | A computed value that changes less often than its inputs, read by composables sensitive to unnecessary recomposition |
🧠 Check your understanding
0/3 · 0/3 answered1. A screen rotates. Which of these values survives the rotation?
2. Why does derivedStateOf almost always need to be wrapped in remember?
3. You need to store a large, complex object that must survive both rotation and process death. What's the best fit?