---
title: "Compose Recomposition at Scale: How Strong Skipping Mode Changes the Stability Rules You Learned"
published: true
description: "A hands-on guide to Jetpack Compose's strong skipping mode — what changed in stability inference, how to diagnose unnecessary recompositions, and practical LazyColumn patterns that actually matter at scale."
tags: kotlin, android, architecture, mobile
canonical_url: https://blog.mvp-factory.com/compose-recomposition-strong-skipping-mode
---
## What You Will Learn
By the end of this tutorial, you will understand how strong skipping mode (default since Compose Compiler 1.5.4+) changes which composables recompose and why. You will set up composition tracing to find performance bottlenecks, and you will apply three concrete patterns that keep LazyColumn smooth when rendering hundreds of complex items.
Let me show you a pattern I use in every project — and the outdated habits you can finally drop.
## Prerequisites
- Android Studio Hedgehog or newer
- Jetpack Compose BOM 2024.02+ (Compose Compiler 1.5.4+)
- Familiarity with `@Composable` functions and `LazyColumn`
## Step 1: Understand What Changed
Before strong skipping, any "unstable" parameter — one the compiler couldn't verify had a reliable `equals()` — forced a full recomposition. Teams responded by sprinkling `@Stable` and `@Immutable` everywhere. Most of those annotations are now dead weight.
Here is the minimal mental model you need:
| Parameter type | Old behavior | Strong skipping behavior |
|---|---|---|
| Unstable params | Always recompose | Skip if same instance (`===`) |
| Unstable lambdas | Always recompose | Auto-memoized by compiler |
| `@Stable` annotated | Skip via `equals()` | Skip via `equals()` (unchanged) |
Strong skipping compares unstable parameters by instance identity. Same object reference? Skip. The annotations now only matter when you need `equals()`-based comparison — like when a repository returns a fresh data class from an API call that is structurally identical.
## Step 2: Set Up Composition Tracing
Layout Inspector shows recomposition counts per composable, but for production-scale diagnosis you want composition tracing. Here is the minimal setup to get this working:
kotlin
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Recomposer.setHotReloadEnabled(true)
}
}
}
Filter logcat for `Recomposer`. The pattern I look for: any composable recomposing more than once per frame during a scroll gesture. That is your signal to dig deeper.
## Step 3: Fix LazyColumn's Blind Spots
Strong skipping auto-memoizes lambdas inside composable functions — but `LazyListScope` is not a composable scope. The docs do not mention this, but it is the single biggest gap teams hit at scale.
kotlin
// This creates a new lambda instance per recomposition
LazyColumn {
items(items) { item ->
ItemCard(
onAction = { viewModel.handleAction(item.id) }
)
}
}
// This gives Compose a stable reference it can skip on
LazyColumn {
items(items, key = { it.id }) { item ->
val onAction = remember(item.id) {
{ viewModel.handleAction(item.id) }
}
ItemCard(onAction = onAction)
}
}
Next, scope your state reads. When a shared `MutableState` is read by item composables, every state change recomposes every visible item:
kotlin
// Every item recomposes when scroll position changes
val scrollOffset = scrollState.value
// Only composables reading showHeader recompose
val showHeader by remember {
derivedStateOf { scrollState.firstVisibleItemIndex > 0 }
}
Finally, always provide stable keys to `items()`. Without them, LazyColumn cannot map items to existing compositions, and any list mutation triggers full recomposition of every visible slot.
## Gotchas
**1. `LazyListScope` lambdas are not auto-memoized.** This is the gotcha that will save you hours. Strong skipping's lambda memoization only applies inside `@Composable` functions. Wrap callbacks with `remember` and a key.
**2. `@Stable` is not useless — it is just narrower.** Keep it when new instances with the same data arrive (API responses, mapped DTOs). Drop it when the ViewModel holds and passes the same reference.
**3. Enums and sealed classes are already inferred stable.** Annotating them is noise. The compiler handles these correctly without help.
**4. Benchmarking without keys gives misleading numbers.** A LazyColumn without `key` will show inflated recomposition counts that disappear once you add keys — profile the real configuration, not the broken one.
## Conclusion
Strong skipping mode made the stability system dramatically less annoying. Audit your `@Stable` and `@Immutable` annotations — if you are on Compose Compiler 1.5.4+, most can go. Profile with composition tracing and Layout Inspector recomposition counts, not gut feeling. And remember that `LazyListScope` is the one place where you still need to manage lambda stability yourself.
Let the compiler do its job. Step in only when your profiler says you should.
**Resources:**
- [Compose Compiler stability docs](https://developer.android.com/develop/ui/compose/performance/stability)
- [Strong skipping mode announcement](https://android-developers.googleblog.com/)
- [Layout Inspector recomposition tracking](https://developer.android.com/studio/debug/layout-inspector)
Top comments (0)