Compose Performance & Stability Quiz

UI › Compose UI

A composable takes a data class with all 'val' properties of primitive/String types. With no extra annotations, how does the compiler classify it?

Answer: Stable/immutable, so the composable can be skippable

The compiler infers a data class with only immutable val properties of stable types as stable/immutable automatically, making the consuming composable skippable without annotations.

Under Strong Skipping Mode, how are parameters whose type is unstable compared between recompositions?

Answer: Using referential/instance equality (===)

Strong Skipping makes such composables skippable but compares unstable parameters by instance equality (===); stable parameters still use equals().

Which change best avoids recomposition when an offset updates every frame during a drag?

Answer: Modifier.offset { IntOffset(x.roundToPx(), y.roundToPx()) } reading state in the lambda

The lambda overload defers the state read to the layout phase, so frequent changes invalidate layout/draw only and skip recomposition; the value-based overload reads in composition and recomposes each frame.

Why does Compose treat a List<Item> parameter as unstable by default?

Answer: List is an interface, so the compiler can’t prove it’s immutable

List/Set/Map are interfaces; the compiler can't prove the backing implementation won't mutate, so it conservatively marks them unstable. kotlinx immutable collections solve this.

What is a key advantage of collectAsStateWithLifecycle over collectAsState on Android?

Answer: It stops collecting once the lifecycle falls below STARTED, sparing background work

collectAsStateWithLifecycle ties collection to the lifecycle (at least STARTED by default), cancelling upstream work in the background; collectAsState keeps collecting while the composition is active.

Which statement about Strong Skipping Mode (Compose Compiler 2.0, Kotlin 2.0.20+) is correct?

Answer: Enabled by default; it also auto-remembers lambdas keyed by their captures

Strong Skipping is on by default since Kotlin 2.0.20+, makes restartable composables skippable regardless of param stability, and auto-wraps lambdas in remember keyed by their captures.

You suspect a list item recomposes too often. Which is the most appropriate first diagnostic step?

Answer: Use Layout Inspector recomposition counts and Compose compiler reports

Layout Inspector recomposition counts pinpoint hotspots and compiler reports reveal which composables are skippable/restartable and which classes are unstable, guiding a targeted fix; benchmarking is done on release builds.

A composable reads listState.firstVisibleItemIndex directly to compute a boolean isScrolled. Wrapping that computation in derivedStateOf improves performance because:

Answer: the observing scope recomposes only when the boolean flips, not every scroll frame

derivedStateOf recomputes whenever its reads change but only notifies observers when the resulting value changes, so scrolling that does not flip the boolean produces no recomposition.

You depend on a model class from a third-party library that the compiler marks unstable, and you cannot edit or annotate its source. What is the recommended fix?

Answer: Use a Compose stability config file to mark the class stable

The Compose compiler supports a stability configuration file (stabilityConfigurationFiles) where you list classes, including wildcard patterns, to be treated as stable when you cannot annotate the original source.

Providing a stable, unique key for each item in a LazyColumn primarily helps performance and correctness by:

Answer: letting Compose match items across data changes so scroll and state survive

Keys let Compose identify items across insertions, removals, and moves, so remembered state is preserved and items that did not change are not needlessly recomposed or recreated.

What does annotating a type @Immutable promise that @Stable does not?

Answer: That public properties never change post-construction, so they never need re-reading

@Immutable is the stronger contract: it guarantees public properties never change after construction, whereas @Stable permits mutation as long as Compose is notified of every change via snapshot state.

Why should Compose performance be benchmarked on a release (R8/minified) build rather than a debug build?

Answer: Debug builds are unoptimized with extra runtime checks, so timings look artificially slow

Debug builds are not optimized by R8 and carry extra runtime checks and instrumentation that exaggerate cost; representative measurements, along with Baseline Profiles, require a release build.

You pass a list created with mutableStateListOf() (a SnapshotStateList) to a composable. How does the compiler classify this parameter?

Answer: Stable, because Compose tracks its snapshot-backed mutations

SnapshotStateList is backed by the snapshot system, so Compose observes its mutations and treats it as stable, unlike a plain MutableList or ArrayList which is inferred unstable.

What problem does movableContentOf (movableContent) solve?

Answer: It keeps a composable's remembered state when it changes layout position

movableContentOf lets the same content be relocated within the composition while keeping its remembered state and underlying nodes alive, instead of disposing and recreating them when its parent or position changes.

Back to Compose Performance & Stability