---
title: "remember vs rememberSaveable: deep dive into state management and recomposition in Jetpack Compose"
description: "Understanding the differences between remember and rememberSaveable by exploring their internal mechanisms, and how they relate to state and recomposition."
language: "en"
publishedAt: "2025-08-08T09:46:48Z"
updatedAt: "2025-08-08T09:46:48Z"
authors:
  - name: "Jaewoong Eum"
    url: "https://www.revenuecat.com/blog/author/jaewoong-eum"
category: "Engineering"
categoryUrl: "https://www.revenuecat.com/blog/engineering"
readingTime: 11
canonical: "https://www.revenuecat.com/blog/engineering/remember-vs-remembersaveable"
---

# remember vs rememberSaveable: deep dive into state management and recomposition in Jetpack Compose

Understanding the differences between remember and rememberSaveable by exploring their internal mechanisms, and how they relate to state and recomposition.

## Table of contents

- [Compose Phases and Recomposition](#compose-phases-and-recomposition)
- [Understanding State](#understanding-state)
  - [State](#state)
- [remember vs. rememberSaveable](#remember-vs-remembersaveable)
  - [remember](#remember)
  - [rememberSaveable](#remembersaveable)
  - [The SaveableHolder: The Internal Workhorse](#the-saveableholder-the-internal-workhorse)
- [Use-cases](#use-cases)
  - [remember](#remember-2)
  - [rememberSaveable](#remembersaveable-2)
- [Conclusion](#conclusion)

remember and rememberSaveable are key APIs for retaining state values across recompositions. In this article, you’ll learn how they differ by exploring their internal mechanisms and understanding their relationship to state and recomposition.

Jetpack Compose enables you to design and build UI components and screens in a declarative way, and this approach is powered by several key concepts. One of the most important is idempotence, the idea that a composable function will always produce the same UI output given the same input parameters. This ensures consistent rendering and helps Compose efficiently manage recompositions and UI updates.

In this article, you’ll explore the concept of recomposition in Jetpack Compose, dive into the internal workings of remember versus rememberSaveable, and learn how these APIs are used in real-world scenarios through examples from [RevenueCat’s Android SDK](https://www.revenuecat.com/docs/getting-started/installation/android).

## Compose Phases and Recomposition

Before we dig into how `remember` and `rememberSaveable` work under the hood, let’s first understand why they’re needed in the first place, and what they actually do in Jetpack Compose.

The Jetpack Compose rendering pipeline is built around [three core phases](https://developer.android.com/develop/ui/compose/phases) that define how UI is constructed and updated: **Composition**, **Layout**, and **Drawing**. These phases work together to form a unidirectional data flow, starting from the UI description and ending with pixels on the screen.

- **Composition** – *What* to show: Composable functions are executed to generate a UI tree that describes the current screen state.
- **Layout** – *Where* to show it: Each element in the tree is measured and positioned within a 2D space, considering constraints and child elements.
- **Drawing** – *How* to render it: The positioned elements are drawn onto a Canvas, typically mapped to the device screen.
Understanding this three-phase pipeline is key to writing a performant and maintainable UI in Compose, especially because these phases are tightly coupled with recomposition. As the name implies, recomposition restarts the pipeline from the composition phase, re-running your composable functions in response to state changes.

This means composable functions are re-invokable at any time, and you should write them assuming that they will be. However, without explicit state-handling mechanisms, every local variable inside a composable will be re-initialized on each recomposition, often leading to unexpected behavior.

That’s why the Compose Runtime introduces State to provide a way for values to survive recomposition and keep your UI in sync with the underlying data model.

## Understanding State

Now, let’s take a moment to understand the nature of State in Jetpack Compose.

For example, consider the following simple function:

```kotlin
@Composable
fun Counter() {
    var counter = 0

    Button(
        onClick = { counter++ }
    ) {
        Text(text = "Count: $counter")
    }
}
```

When you click the button, the `counter` variable should increase by 1, and the `Counter` composable function should re-render to reflect the updated state. However, it won’t behave as expected. That’s because Compose doesn’t automatically track regular variables, it relies on a special mechanism to detect changes and trigger recomposition.

### State

To handle this properly, Compose provides a built-in mechanism called State. State allows the Compose runtime to observe value changes and automatically re-run the associated composable functions when needed. Once a state value changes, Compose will intelligently recompose only the affected parts of the UI.

Let’s update the function to use State below:

```kotlin
@Composable
fun Counter() {
    var counter by mutableStateOf(0)

    Button(
        onClick = { counter++ }
    ) {
        Text(text = "Count: $counter")
    }
}
```

It looks like it should work, but you’ll notice a red underline under `mutableStateOf`, with an error saying: “Creating a state object during composition without using remember.”

So what’s the issue? Let’s break it down.

A function is an independently executable block of code, and in Jetpack Compose, a composable function can be re-executed at any time due to state changes (i.e. recomposition). That means the `counter` variable is re-initialized to `0` every time `Counter()` is recomposed.

To persist the value across recompositions, you need to tell Compose to retain it in memory, and that’s exactly what `remember` is for.

## remember vs. rememberSaveable

To persist state across recompositions, Jetpack Compose provides two essential tools: `remember` and `rememberSaveable`. These APIs allow you to retain values even as your composable functions are re-executed, ensuring the UI remains consistent with user interactions.

Let’s revise the earlier example using `remember` to preserve the counter value:

```kotlin
@Composable
fun Counter() {
    var counter by remember { mutableStateOf(0) }

    Button(onClick = { counter++ }) {
        Text(text = "Count: $counter")
    }
}
```

In this case, every time the `Counter` composable is recomposed, for example, after a state update, the current `counter` value is preserved in memory instead of being reinitialized.

Both `remember` and `rememberSaveable` serve a similar purpose: they cache values in the Compose runtime memory, giving the illusion of persistence within an otherwise stateless function. However, while they may appear similar on the surface, their behaviors diverge significantly behind the scenes.

In short, `remember` stores values in memory only as long as the process remains alive. If the app process is killed (e.g., backgrounded and reclaimed by the system), the state is lost. On the other hand, `rememberSaveable` serializes and stores the state in a `Bundle`, allowing it to survive configuration changes and process death, making it a better choice for preserving UI state across a wider range of lifecycle events.

We’ll explore those differences in more detail next.

### remember

If you look into the internal implementation of `remember`, you’ll see it’s quite straightforward something like this:

```kotlin
@Composable
inline fun <T> remember(
    key1: Any?,
    crossinline calculation: @DisallowComposableCalls () -> T
): T {
    return currentComposer.cache(currentComposer.changed(key1), calculation)
}
```

The `remember` function uses the **Composer** to cache a value in memory. This cached value is preserved across recompositions unless the provided `key` changes, which is how `remember` knows when to invalidate and recreate the value.

### rememberSaveable

In Jetpack Compose, `remember` is an API for maintaining state across recompositions. However, its memory is ephemeral; it does not survive configuration changes or, more critically, system-initiated process death. This is where `rememberSaveable` steps in. It builds upon the foundation of `remember` but adds a crucial layer of persistence, ensuring that UI state can be saved to and restored from a `Bundle`, the standard Android mechanism for instance state.

A detailed analysis of its internal implementation reveals an internal mechanism that interacts with a `SaveableStateRegistry`, uses a `Saver` to convert objects into bundle-able types, and leverages the `RememberObserver` lifecycle to manage its registration. Let’s dissect the code to understand how it achieves this resilience.

```kotlin
@Composable
fun <T : Any> rememberSaveable(
    vararg inputs: Any?,
    saver: Saver<T, out Any> = autoSaver(),
    key: String? = null,
    init: () -> T
): T {
    \/\/ 1. Determine a Unique Key
    val compositeKey = currentCompositeKeyHash
    val finalKey = if (!key.isNullOrEmpty()) {
        key
    } else {
        compositeKey.toString(MaxSupportedRadix)
    }

    \/\/ 2. Access the Registry
    val registry = LocalSaveableStateRegistry.current

    \/\/ 3. Remember the Holder Object
    val holder = remember {
        val restored = registry?.consumeRestored(finalKey)?.let { saver.restore(it) }
        val finalValue = restored ?: init()
        SaveableHolder(saver, registry, finalKey, finalValue, inputs)
    }

    \/\/ 4. Value and SideEffect for Updates
    val value = holder.getValueIfInputsDidntChange(inputs) ?: init()
    SideEffect { holder.update(saver, registry, finalKey, value, inputs) }

    return value
}
```

Let’s break down this orchestration step by step:

1. **Determining a Unique Key:** The first and most critical step is establishing a unique key for the state being saved. This key is used to store and retrieve the value from the `Bundle`. The function prioritizes a developer-provided `key`. If none is given, it falls back to using the `currentCompositeKeyHash`, a value automatically generated by the Compose compiler based on the composable’s position in the UI tree. This ensures that the state is uniquely identified within its parent.
1. **Accessing the Registry:** It then looks up the `SaveableStateRegistry` from a `CompositionLocal`. This registry is the central authority responsible for the entire save/restore process. It’s the component that actually interacts with the Android framework’s `SavedStateHandle` or `Activity.onSaveInstanceState`.
1. **Remembering the **`**SaveableHolder**`**:** This is the core of the implementation. Instead of just remembering the value itself, it remembers an instance of a private helper class, `SaveableHolder`. This holder is where all the complex logic resides. During its initial creation inside `remember`, it attempts to restore a value. It asks the `registry` if it has a previously saved value for the `finalKey` (`consumeRestored`). If it does, it uses the provided `saver` object to convert the stored data back into its original type `T` (`saver.restore(it)`). If no restored value is found (e.g., on first launch), it uses the `init` lambda to create the initial value. This `SaveableHolder` is then stored in the composition’s memory.
1. **Handling Inputs and Updates:** The `vararg inputs` parameter behaves similarly to the keys in `LaunchedEffect`. If the inputs change on a subsequent recomposition, it signals that the state should be reset. The line `holder.getValueIfInputsDidntChange(inputs) ?: init()` enforces this. The `SideEffect` ensures that any changes to the parameters (`saver`, `registry`, `key`, `value`, `inputs`) are communicated to the `holder` after every successful recomposition.
`rememberSaveable` is not a simple function but an intricate system designed to bridge the declarative, in-memory world of Compose with the persistent, lifecycle-aware world of the Android framework. It uses a unique **key** to identify state and a `**SaveableStateRegistry**` to manage the actual saving process.

The core logic is encapsulated within a `**SaveableHolder**` object, which is stored in the composition via `remember`. This holder acts as a `RememberObserver`, using `onRemembered` to **register a value provider** with the registry and `onForgotten` to **unregister** it, thus perfectly synchronizing the persistence lifecycle with the composition lifecycle. By also tracking `inputs`, it provides a powerful mechanism to not only survive process death but also to control when state should be reset based on external dependencies.

Now let’s explore `SaveableHolder` .

### The SaveableHolder: The Internal Workhorse

The `SaveableHolder` class is a private implementation detail that encapsulates all the logic for interacting with the `SaveableStateRegistry`. It implements `RememberObserver` to hook into the composition lifecycle.

Let’s analyze its key responsibilities:

```kotlin
private class SaveableHolder<T>(
    \/\/ ... constructor properties ...
) : SaverScope, RememberObserver {
    private var entry: SaveableStateRegistry.Entry? = null
    private val valueProvider = { \/* saves the value *\/ }

    \/\/ ... update function ...
    fun update(
        saver: Saver<T, Any>,
        registry: SaveableStateRegistry?,
        key: String,
        value: T,
        inputs: Array<out Any?>
    ) {
     ..
    }

    private fun register() {
        \/\/ ... registers the valueProvider with the registry ...
    }

    override fun onRemembered() {
        register()
    }

    override fun onForgotten() {
        entry?.unregister()
    }

    \/\/ ... other functions ...
}
```

- **Registration with the Registry (**`**register**`** and **`**onRemembered**`**):** The `SaveableHolder`‘s most important job is to register itself with the `SaveableStateRegistry`. When the holder is first “remembered” by the composition, its `onRemembered()` lifecycle callback is invoked. This, in turn, calls the private `register()` function. Inside `register()`, it doesn’t pass its value directly to the registry. Instead, it provides a lambda, `valueProvider`. This provider, when called by the registry during the `onSaveInstanceState` event, will execute the `saver` logic to convert the current value into a `Bundle`compatible format. The registry returns an `Entry` object, which is a handle that can be used later to unregister.
- **Unregistering (**`**onForgotten**`**):** When the composable leaves the composition (or its `rememberSaveable` keys change), the `onForgotten()` callback is triggered. This is the cleanup phase. The holder calls `entry?.unregister()`, telling the `SaveableStateRegistry` that it no longer needs to save this piece of state. This prevents memory leaks and ensures that state from disposed UI is not saved unnecessarily.
- **Updating State (**`**update**`**):** The `update` function is called within the `SideEffect` block. Its job is to keep the holder’s internal properties (like the current `value`, the `saver`, and the `key`) synchronized with the latest values from the composable function. If critical properties like the `key` or the `registry` itself change, it knows that its current registration is invalid, so it unregisters and re-registers itself to ensure correctness.
- **Handling Input Changes (**`**getValueIfInputsDidntChange**`**):** This function is a simple but effective check. Before returning its stored value, it compares the current `inputs` from the recomposition with the `inputs` it stored from the previous composition. If they don’t match, it returns `null`, signaling to the `rememberSaveable` composable that the state should be reset to its `init` value. This is how `rememberSaveable` achieves the behavior of re-initializing when its keys change.
## Use-cases

Now that you’ve explored the internals of `remember`, `rememberSaveable`, and their supporting components like `SaveableStateRegistry` and `SaveableHolder`, the next question is: **When should you actually use each one?**

At first glance, `rememberSaveable` might seem like the better option, it can persist across both recompositions *and* process death. So should you just use it everywhere instead of `remember`?

Let’s walk through some practical use cases from RevenueCat’s Android SDK to understand when each API is appropriate, and why picking the right one matters for both performance and user experience.

### remember

As discussed earlier, `remember` preserves state across recompositions, but it does not restore values after configuration changes (e.g., rotations or process death). In [RevenueCat’s Android SDK](https://github.com/RevenueCat/purchases-android) demo, we use `remember` with `mutableStateOf` to hold UI state during recomposition, like this:

```kotlin
var isDebugBottomSheetVisible by remember { mutableStateOf(false) }
var showLogInDialog by remember { mutableStateOf(false) }
var showApiKeyDialog by remember { mutableStateOf(false) }
```

So those values will be re-initialized to `false` if a configuration change occurs, such as rotating the screen, switching to dark mode, or adjusting the system font size.

### rememberSaveable

On the other hand, the RevenueCat SDK uses `rememberSaveable` to persist specific state not only across recompositions, but also through configuration changes, as seen in the `PaywallDialog` example.

```kotlin
@Composable
fun PaywallDialog(
    paywallDialogOptions: PaywallDialogOptions,
) {
    val shouldDisplayBlock = paywallDialogOptions.shouldDisplayBlock
    var shouldDisplayDialog by rememberSaveable { mutableStateOf(shouldDisplayBlock == null) }
    if (shouldDisplayBlock != null) {
        LaunchedEffect(paywallDialogOptions) {
            launch {
                shouldDisplayDialog = shouldDisplayPaywall(shouldDisplayBlock)
            }
        }
    }
```

In this case, the paywall dialog should remain visible if the user isn’t entitled to access premium features, even after a configuration change. If you used `remember` instead of `rememberSaveable`, the `shouldDisplayDialog` state could be reset to `false` during such changes, like screen rotation, dark mode toggling, or font size adjustments, which might lead to unintended behavior, such as granting access to premium features without proper entitlement.

So for the same use cases of state to display a dialog, which one you should use can be very different on the situation. Then, does using `rememberSaveable` everywhere instead of `remember` is better since it has more features and retain better? The answer is no.

Retaining all states in every scenario can also cause unintended behavior, and as you’ve seen in the internal implementation, `rememberSaveable` carries more overhead than `remember`. That’s why it’s important to carefully choose the right API based on your specific requirements, balancing persistence needs against performance considerations.

## Conclusion

In this article, you’ve covered the core concepts of Compose phases, recomposition, state, and the internal workings of `remember` and `rememberSaveable` along with guidance on selecting the right API for your needs. Together, these concepts form the cornerstone of the Compose Runtime, enabling Jetpack Compose to deliver a truly declarative UI experience.

As always, happy coding!

— [Jaewoong](https://github.com/skydoves)

---

## Related posts

- [Server-driven UI SDK on Android: how RevenueCat enables remote paywalls without app updates](https://www.revenuecat.com/blog/engineering/server-driven-android)
- [Turn Your App into Revenue: Building Paywalls in Android With Jetpack Compose](https://www.revenuecat.com/blog/engineering/build-paywalls-compose)
- [Ensure public interface reliability: Tracking API compatibility for Android and Kotlin](https://www.revenuecat.com/blog/engineering/binary-compatability)
