Jetpack Compose Performance: Demystifying Strong Skipping Mode

Jetpack Compose Performance: Demystifying Strong Skipping Mode

Historically, one of the primary performance pain points in Jetpack Compose was dealing with class stability. If a composable function accepted an unstable type (such as standard Java/Kotlin collections or classes from external libraries), Compose could not verify if the values had changed. Consequently, it had to re-run the composable during every single recomposition, leading to dropped frames.

To fix this, we used to clutter our codebases with @Stable and @Immutable annotations, or write custom wrappers for list structures.

But since Compose Compiler 1.5.4, and fully matured in 2026, Strong Skipping Mode has changed the game. Let’s explore how it works and how it simplifies UI performance optimization.

What is Strong Skipping Mode?

Strong Skipping Mode changes how the Compose Compiler decides which composable functions are skippable:

  1. Unstable parameters become skippable: Composables with unstable parameters are now marked as skippable. During recomposition, Compose compares unstable parameters using instance equality (reference equality: ===).
  2. Lambdas with unstable captures are memoized: Lambdas inside composables that capture unstable classes are automatically wrapped in a remember call, preventing them from recreating on every single recomposition.

Strong Skipping Mode in Action

Before Strong Skipping Mode, the following code would recompose the UserList component every time triggerState changed, even if the user list itself remained identical, because List<User> is considered unstable:

@Composable
fun DashboardScreen(viewModel: DashboardViewModel) {
    val users by viewModel.users.collectAsState()
    var triggerState by remember { mutableStateOf(false) }

    Column {
        Button(onClick = { triggerState = !triggerState }) {
            Text("Toggle Internal State: $triggerState")
        }
        UserList(users = users) // Unstable list parameter!
    }
}

@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        items(users) { user ->
            UserRow(user)
        }
    }
}

With Strong Skipping Mode enabled, Compose compares the users reference. If the list instance hasn’t changed (i.e. oldUsers === newUsers), the execution of UserList is skipped completely. This prevents major frame drops on complex dynamic lists.

Enabling Strong Skipping Mode

If you are using Jetpack Compose 1.7.0 or modern 2026 releases with Compose Multiplatform, Strong Skipping Mode is enabled by default. If you are on an older compiler, enable it inside your Gradle configuration:

composeCompiler {
    enableStrongSkippingMode = true
}

When to Still Use @Stable / @Immutable

While Strong Skipping Mode makes UI optimization much easier, it relies on reference equality (===) for unstable objects. If your app frequently instantiates new instances of objects containing identical data, reference equality will fail, and recomposition will still occur.

In these specific scenarios, explicitly marking your domain models with @Immutable or @Stable is still necessary to enforce value equality checks:

@Immutable
data class LegacyUser(
    val id: String,
    val name: String
)

By understanding this behavior, you can leverage Strong Skipping Mode to write clean, boilerplate-free Compose layouts without compromising UI performance.