Skip to content

Compose


What makes Jetpack Compose a declarative UI toolkit?

beginner compose fundamentals ui
View Answer

Compose is declarative because UI is a function of state.

You describe what UI should look like for current state, and Compose updates it when state changes.

Key interview points:

  • no manual view mutation for most updates

  • composables are state-driven functions

  • runtime handles incremental UI updates

  • easier unidirectional data flow patterns

๐Ÿš€ See Full Deep Dive


What is a composable function?

beginner compose composables ui
View Answer

A composable function is a Kotlin function annotated with @Composable that emits UI into the Compose tree.

Important details:

  • called from other composables

  • can read state and react to changes

  • should be side-effect free in the UI phase

  • participates in recomposition and skipping

๐Ÿš€ See Full Deep Dive


How should you think about composable lifecycle compared to Activity lifecycle?

intermediate compose lifecycle architecture
View Answer

Composables do not have a lifecycle identical to Activities or Fragments.

They can enter and leave composition many times based on state and tree changes.

In interviews, explain:

  • composable lifetime is composition-scoped

  • effects must be tied to composition lifecycle

  • remember state is lost when node leaves composition

  • business state should live in ViewModel/domain layers

๐Ÿš€ See Full Deep Dive


What are Compose previews and their limitations?

beginner compose tooling previews
View Answer

Previews render composables in Android Studio without running full app flow.

Useful for quick UI iteration and visual validation.

Limitations to mention:

  • limited runtime environment

  • navigation and dependency graphs may need fakes

  • async side effects can behave differently

  • not a replacement for UI tests

๐Ÿš€ See Full Deep Dive


What is MutableState in Compose?

beginner compose state runtime
View Answer

MutableState<T> is an observable state holder integrated with Compose snapshots.

When value changes, composables that read it become eligible for recomposition.

Typical usage:

  • var text by remember { mutableStateOf("") }

  • local UI state in composables

  • immutable state models for larger screens

  • avoid mutating nested mutable objects without state wrappers

๐Ÿš€ See Full Deep Dive


What is the difference between remember and rememberSaveable?

intermediate compose state configuration
View Answer

remember keeps state across recompositions while the composable stays in composition.

rememberSaveable also survives Activity recreation using saved instance state.

Interview-ready distinction:

  • remember: transient UI memory only

  • rememberSaveable: survives rotation/process recreation scenarios

  • rememberSaveable needs savable types or custom Saver

  • long-lived/business state still belongs in ViewModel

๐Ÿš€ See Full Deep Dive


Why do keys matter in remember?

intermediate compose remember recomposition
View Answer

Keys control when remembered value should be recreated.

If keys change, Compose discards old remembered value and computes a new one.

Practical points:

  • use stable identity inputs as keys

  • missing keys can keep stale state

  • over-changing keys can cause unnecessary resets

  • same concept appears in list item keys

๐Ÿš€ See Full Deep Dive


What is state hoisting in Compose?

intermediate compose state architecture
View Answer

State hoisting means moving state ownership to a higher-level composable and passing state + events down.

Why it is preferred:

  • improves reusability and testability

  • keeps child composables stateless when possible

  • aligns with unidirectional data flow

  • simplifies screen-level orchestration with ViewModel

๐Ÿš€ See Full Deep Dive


How does unidirectional data flow apply in Compose UI architecture?

intermediate compose architecture state
View Answer

In UDF, state flows downward and events flow upward.

Typical pattern:

  • ViewModel exposes immutable UI state

  • composable renders that state

  • user events are callbacks to ViewModel

  • ViewModel reduces events into new state

This reduces hidden mutations and makes behavior predictable.

๐Ÿš€ See Full Deep Dive


How should UI state be modeled for complex Compose screens?

senior compose architecture state
View Answer

Model screen state as immutable data classes with explicit sub-states (loading, content, error, empty).

Interview-friendly guidance:

  • one source of truth per screen

  • keep transient one-off events separate from state

  • avoid many unrelated mutable flags

  • design state to match rendering branches

๐Ÿš€ See Full Deep Dive


What are best practices for event handling in Compose?

intermediate compose architecture events
View Answer

Events should be explicit callbacks from UI to state owner.

Good interview answer includes:

  • pass lambdas like onRetry, onItemClick

  • avoid business logic inside composables

  • map UI events to intents/actions in ViewModel

  • keep event handling idempotent when possible

๐Ÿš€ See Full Deep Dive


What is recomposition in Jetpack Compose?

intermediate compose recomposition runtime
View Answer

Recomposition is re-execution of composable functions whose observed state changed.

Compose tracks state reads and only re-runs affected parts of the tree.

Key concepts to mention:

  • state read tracking

  • invalidation of composition scopes

  • selective updates instead of full redraw

  • skip optimization for unchanged inputs

๐Ÿš€ See Full Deep Dive


What triggers recomposition?

intermediate compose recomposition state
View Answer

Recomposition is triggered when snapshot-observed state used by a composable changes.

Common triggers:

  • MutableState value updates

  • new values emitted via collected flows

  • changed parameters from parent composable

  • structural tree changes (conditionals/lists)

Not every state change causes full-screen recomposition.

๐Ÿš€ See Full Deep Dive


What is smart recomposition?

intermediate compose recomposition optimization
View Answer

Smart recomposition means Compose re-runs only invalidated scopes, not the entire UI tree.

It relies on:

  • state read boundaries

  • restart groups in compiler-generated code

  • parameter change checks

  • skipping groups when inputs are stable and unchanged

๐Ÿš€ See Full Deep Dive


What is skip optimization in Compose?

senior compose recomposition compiler
View Answer

Skip optimization lets Compose avoid re-running a composable group when its inputs are considered unchanged.

Interview points:

  • depends on stability analysis and equality checks

  • unstable parameters reduce skipping opportunities

  • fewer skipped groups can increase frame cost

  • metrics/tools can reveal skip behavior

๐Ÿš€ See Full Deep Dive


Why do unstable parameters often cause extra recomposition?

senior compose stability performance
View Answer

Unstable types are assumed to potentially change in ways Compose cannot safely infer, so groups become less skippable.

Consequences:

  • more recomposition work

  • harder performance tuning

  • frequent invalidation in list-heavy screens

  • pressure on frame budget

๐Ÿš€ See Full Deep Dive


How do you reduce unnecessary recomposition in production apps?

senior compose recomposition performance
View Answer

Reduce recomposition by improving state boundaries and input stability.

Practical tactics:

  • hoist and split state by UI responsibility

  • pass stable, minimal parameters

  • use derivedStateOf for derived expensive values

  • provide keys in lazy lists

  • profile with layout inspector and tracing

๐Ÿš€ See Full Deep Dive


What is the Compose snapshot system?

senior compose snapshot runtime
View Answer

Snapshots are Compose runtime's state consistency mechanism.

They track reads/writes to observable state and coordinate safe updates with recomposition.

Interview highlights:

  • MVCC-like model for state access

  • change application invalidates readers

  • enables thread-safe state transactions with rules

  • foundation for automatic UI reactivity

๐Ÿš€ See Full Deep Dive


How are state reads and writes observed by Compose runtime?

senior compose snapshot recomposition
View Answer

During composition, runtime records state reads per scope.

Later, writes to those state objects invalidate dependent scopes and schedule recomposition.

This model provides:

  • precise dependency tracking

  • selective invalidation

  • deterministic update behavior

  • better performance than coarse full-tree updates

๐Ÿš€ See Full Deep Dive


Why does Compose provide side-effect APIs?

intermediate compose side-effects runtime
View Answer

Composables should describe UI, but apps still need imperative work (coroutines, listeners, analytics, cleanup).

Side-effect APIs provide lifecycle-aware hooks for that work.

Mention in interviews:

  • choose API by lifecycle and restart behavior

  • avoid launching side effects directly in composable body

  • keep effects scoped and cancelable

๐Ÿš€ See Full Deep Dive


When should SideEffect be used?

intermediate compose side-effects runtime
View Answer

SideEffect runs after every successful recomposition commit.

Use it to publish Compose state to non-Compose objects that need latest values on each commit.

Guardrails:

  • keep work fast and idempotent

  • avoid long-running jobs in SideEffect

  • prefer LaunchedEffect for suspend work

๐Ÿš€ See Full Deep Dive


What problem does produceState solve?

intermediate compose state side-effects
View Answer

produceState bridges external async sources into Compose State<T>.

It launches a coroutine tied to composition and updates value.

Useful for:

  • repository/data source integration

  • converting callbacks or suspend fetches to UI state

  • lifecycle-scoped loading without manual job wiring

๐Ÿš€ See Full Deep Dive


How does LaunchedEffect work and when should you use it?

intermediate compose side-effects coroutines
View Answer

LaunchedEffect(keys...) starts a coroutine when entering composition, and restarts it when keys change.

Use it for composition-scoped suspend work.

Interview points:

  • cancellation on leaving composition

  • key changes restart effect

  • good for one-off screen tasks and collectors

  • avoid using unstable/changing keys unintentionally

๐Ÿš€ See Full Deep Dive


When do you use DisposableEffect?

intermediate compose side-effects lifecycle
View Answer

DisposableEffect(keys...) is for registering something that needs cleanup, such as listeners or observers.

It provides onDispose for deterministic teardown.

Common interview examples:

  • register/unregister lifecycle observer

  • subscribe/unsubscribe callback APIs

  • resource attach/detach tied to composition

๐Ÿš€ See Full Deep Dive


What is rememberCoroutineScope used for?

intermediate compose coroutines side-effects
View Answer

rememberCoroutineScope returns a composition-aware CoroutineScope you can use from callbacks like button clicks.

Difference from LaunchedEffect:

  • LaunchedEffect: automatic launch during composition

  • rememberCoroutineScope: manual launch on events

  • both cancel when leaving composition

  • still keep business logic in ViewModel when appropriate

๐Ÿš€ See Full Deep Dive


What is derivedStateOf and when does it help?

intermediate compose state performance
View Answer

derivedStateOf memoizes derived values from other state objects and recalculates only when dependencies change.

It helps when:

  • derivation is non-trivial

  • source state updates frequently

  • you want to reduce unnecessary downstream recomposition

  • computed value is consumed by multiple UI branches

๐Ÿš€ See Full Deep Dive


Why is rememberUpdatedState important in long-lived effects?

senior compose side-effects state
View Answer

rememberUpdatedState gives effects access to the latest lambda/value without restarting the effect.

This is useful when restart would be expensive or semantically wrong.

Typical use case:

  • timer/listener effect keeps running

  • callback reference updates on recomposition

  • effect reads latest callback safely

๐Ÿš€ See Full Deep Dive


What is CompositionLocal and when should it be used?

intermediate compose architecture compositionlocal
View Answer

CompositionLocal passes values implicitly down the composition tree without threading parameters through each layer.

Best for cross-cutting concerns like theme, density, or environment values.

Cautions:

  • avoid hiding business dependencies

  • document provided locals clearly

  • overuse can hurt readability and testability

๐Ÿš€ See Full Deep Dive


How do you integrate StateFlow with Compose UI?

intermediate compose stateflow architecture
View Answer

Expose UI state from ViewModel as StateFlow<UiState> and collect in UI.

Compose side typically uses lifecycle-aware collection API.

Interview points:

  • single source of truth in ViewModel

  • immutable state objects

  • Compose observes and renders latest state

  • events flow back via callbacks

๐Ÿš€ See Full Deep Dive


collectAsState vs collectAsStateWithLifecycle - what is the difference?

intermediate compose stateflow lifecycle
View Answer

Both convert Flow emissions to Compose State.

collectAsStateWithLifecycle adds Android lifecycle awareness, reducing unnecessary collection when UI is not active.

Interview framing:

  • prefer lifecycle-aware API on Android screens

  • plain collectAsState is fine in non-lifecycle contexts

  • prevents background collection leaks/waste

๐Ÿš€ See Full Deep Dive


What is snapshotFlow and when would you use it?

senior compose flow snapshot
View Answer

snapshotFlow converts reads of Compose snapshot state into a cold Flow.

It is useful when coroutine/Flow pipelines need Compose state changes.

Good examples:

  • track scroll thresholds

  • debounce UI-derived signals

  • bridge Compose state to repository/analytics layers

๐Ÿš€ See Full Deep Dive


What does stability mean in Compose?

senior compose stability compiler
View Answer

Stability describes whether a type can be reliably checked for meaningful change to support skipping recomposition.

Stable inputs improve skippability and runtime efficiency.

Interview signals:

  • inferred by compiler + annotations

  • immutable data patterns help

  • mutable public properties often hurt stability

๐Ÿš€ See Full Deep Dive


What is the difference between @Stable and @Immutable?

senior compose stability annotations
View Answer

@Immutable indicates object state does not change after construction.

@Stable indicates changes are observable and equality semantics are reliable for Compose optimization.

Interview caveat:

  • annotations are contracts, not magic performance buttons

  • misuse can lead to stale UI or incorrect assumptions

๐Ÿš€ See Full Deep Dive


What is the role of the Compose compiler?

senior compose compiler runtime
View Answer

The Compose compiler transforms composable code into runtime calls that manage composition, recomposition, and skipping.

Key outcomes:

  • inserts restart/skip groups

  • adds change-tracking parameters

  • performs stability-driven optimizations

  • enables tooling metrics for analysis

๐Ÿš€ See Full Deep Dive


What is the Slot Table in Compose runtime?

staff compose runtime slot-table
View Answer

Slot Table is the runtime data structure that stores composition groups, remembered values, and positional metadata.

It enables efficient tree updates without rebuilding everything.

Interview highlights:

  • positional memoization model

  • group identity and structure tracking

  • powers remember and node reuse behavior

๐Ÿš€ See Full Deep Dive


What are Composer and Applier in Compose internals?

staff compose internals runtime
View Answer

Composer records and reconciles composition operations. Applier applies resulting tree changes to target UI tree implementation.

In Android UI:

  • Composer decides what changed

  • Applier performs node insert/move/remove updates

  • separation supports different tree backends

๐Ÿš€ See Full Deep Dive


What are the major runtime phases in Compose frame updates?

senior compose runtime rendering
View Answer

Compose update pipeline can be framed as:

  • composition (compute UI structure)

  • layout (measure/place)

  • drawing (render to canvas)

Recomposition affects composition, but layout/draw can run independently when only size/visual invalidations occur.

๐Ÿš€ See Full Deep Dive


Why does modifier order matter in Compose?

intermediate compose modifier ui
View Answer

Modifiers are applied in sequence, and each step wraps or transforms behavior.

Reordering can change:

  • layout size constraints

  • drawing/clipping outcome

  • pointer input hit areas

  • semantics/accessibility output

Explain with simple examples like padding before vs after clickable.

๐Ÿš€ See Full Deep Dive


What should you know before writing custom layouts in Compose?

senior compose layout performance
View Answer

Understand constraints-driven measurement and explicit placement APIs.

Core interview points:

  • measure children with provided constraints

  • place children in layout block

  • avoid repeated expensive measurement work

  • preserve predictable intrinsic sizing behavior

๐Ÿš€ See Full Deep Dive


Explain measure, layout, and draw phases in Compose.

intermediate compose layout rendering
View Answer

Measure determines child sizes under constraints. Layout places children in parent coordinates. Draw renders pixels based on final layout tree.

Important interview note:

  • not every state change forces all three phases equally

  • phase-specific invalidations are key for performance

๐Ÿš€ See Full Deep Dive


How do you optimize LazyColumn performance?

senior compose lazycolumn performance
View Answer

Optimize item stability, keys, and per-item work.

Practical checklist:

  • provide stable item keys

  • avoid heavy allocations in item content

  • keep item state scoped correctly

  • minimize nested lazy containers

  • profile jank with real datasets

๐Ÿš€ See Full Deep Dive


Why are keys important in LazyColumn items?

intermediate compose lazycolumn state
View Answer

Keys preserve item identity across insertions, deletions, and moves.

Without stable keys:

  • item state can jump to wrong rows

  • animations and reuse become less predictable

  • recomposition work can increase

  • scroll position behavior may degrade

๐Ÿš€ See Full Deep Dive


What are core principles of navigation in Compose?

intermediate compose navigation architecture
View Answer

Use a route graph with explicit destinations and argument contracts.

Strong interview answer includes:

  • single NavHost per feature shell/app shell

  • pass IDs, not large objects

  • keep navigation decisions near state owner

  • support deep links and back stack predictability

๐Ÿš€ See Full Deep Dive


How do you keep navigation maintainable at scale in Compose apps?

senior compose navigation architecture
View Answer

Treat destinations as typed contracts and centralize route definitions.

Maintainability practices:

  • sealed route models or typed destinations

  • feature-level navigation modules

  • avoid scattering route strings

  • make back stack and result passing explicit

๐Ÿš€ See Full Deep Dive


How does theming work in Compose with Material 3?

intermediate compose theming material3
View Answer

Material theme is provided via composition locals (color scheme, typography, shapes) and consumed by Material components.

Interview points:

  • app-level theme as design system boundary

  • support light/dark and dynamic color strategy

  • keep custom tokens consistent with brand system

  • avoid hardcoded colors in feature UI

๐Ÿš€ See Full Deep Dive


What animation APIs should you discuss in Compose interviews?

intermediate compose animation ui
View Answer

Focus on choosing API by use case complexity.

Common set:

  • animate*AsState for simple value transitions

  • AnimatedVisibility and AnimatedContent for content transitions

  • updateTransition for coordinated multi-property animations

  • infinite/repeatable animations for decorative motion

๐Ÿš€ See Full Deep Dive


What is a strong testing strategy for Compose UI?

senior compose testing architecture
View Answer

Use a testing pyramid: state logic tests, composable behavior tests, and targeted integration/end-to-end flows.

Interview-ready points:

  • verify semantics, not implementation details

  • inject fake state/data for deterministic tests

  • isolate flaky async behavior with test dispatchers

  • keep UI tests focused on high-value user journeys

๐Ÿš€ See Full Deep Dive


How do semantics and test tags help Compose testing?

intermediate compose testing semantics
View Answer

Compose tests query semantics tree, not view IDs.

Modifier.testTag() and semantic properties provide stable selectors.

Best practices:

  • prefer meaningful semantics for accessibility + tests

  • avoid brittle text-only selectors when dynamic

  • keep tags unique within test scope

๐Ÿš€ See Full Deep Dive


When and how should you use AndroidView interop?

senior compose interoperability views
View Answer

AndroidView embeds legacy View-based UI inside Compose.

Use it for incremental migration or SDK widgets not available in Compose.

Interview cautions:

  • manage View lifecycle and state sync carefully

  • avoid frequent View recreation

  • keep interop boundaries explicit and temporary when possible

๐Ÿš€ See Full Deep Dive


What is your practical Compose performance checklist?

staff compose performance optimization
View Answer

A strong answer balances architecture and runtime-level tuning.

Quick checklist:

  • stabilize data models and parameters

  • reduce unnecessary recomposition scopes

  • optimize lazy list item content and keys

  • move heavy work off main thread

  • measure with tracing, profiler, and macrobenchmark

๐Ÿš€ See Full Deep Dive


Compare rememberCoroutineScope, LaunchedEffect, and ViewModel scope โ€” when to use each

intermediate compose coroutines effects viewmodel
View Answer

Each Compose coroutine-launching API has a different lifecycle and cancellation owner; mixing them incorrectly leads to leaks or effects that run at the wrong time.

In interviews, cover:

  • LaunchedEffect(key): side effect tied to composition โ€” launches on entry and cancels/relaunches when key changes or composition leaves; correct for data loading, animations, event observation

  • rememberCoroutineScope(): returns a CoroutineScope tied to the current composition lifecycle; used for user-triggered one-shot actions (button tap launches network call) where you need manual control over when a coroutine starts

  • ViewModel scope (viewModelScope): survives recomposition and configuration changes; the right place for data loading and business logic that outlives a composable

Strong answer tip:

  • anti-pattern: launching network calls inside LaunchedEffect(Unit) that directly mutates ViewModel state from the composable layer โ€” move the logic to ViewModel and collect resulting state in the composable

๐Ÿš€ See Full Deep Dive


How do you model one-off events in Compose โ€” navigation, snackbar, and toast correctly

intermediate compose events navigation snackbar udf
View Answer

One-off events (navigation, snackbars, toasts) cannot use StateFlow because recomposition or rotation would re-trigger them; they need a single-delivery mechanism.

In interviews, cover:

  • SharedFlow(replay=0, extraBufferCapacity=1) in ViewModel; collect in LaunchedEffect(viewModel) in the composable; each emission is consumed once

  • Channel.receiveAsFlow() is an alternative with FIFO ordering and back-pressure awareness; choose based on whether you need buffering

  • do NOT put navigation events in a StateFlow โ€” new collectors (after recomposition) receive the last value and may navigate again (double navigation bug)

  • the "event wrapper" pattern (one-shot wrappers like Event) is a workaround that adds complexity; prefer proper flow primitives

Strong answer tip:

  • demonstrate the double-navigation bug: StateFlow initialized with null, ViewModel sets it to Route.Details, user rotates, Compose re-collects state and navigates again โ€” fix: clear after consuming or use SharedFlow

๐Ÿš€ See Full Deep Dive


Explain derivedStateOf and how to avoid unnecessary recompositions

intermediate compose performance derived-state recomposition
View Answer

derivedStateOf creates a computed snapshot state that only notifies observers when its output changes, not every time its inputs change โ€” preventing unnecessary recompositions.

In interviews, cover:

  • problem: reading a LazyListState.firstVisibleItemIndex in a composable to show a "scroll to top" FAB causes recomposition on every scroll pixel because the MutableState backing firstVisibleItemIndex changes constantly

  • solution: val showFab by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } โ€” only recomposes when the Boolean flips, not on every index change

  • semantics: derivedStateOf creates a new snapshot state; reads inside it are tracked; when inputs change the output is recomputed but observers are only invalidated if the output is different (by equals)

  • remember wrapping is required: without remember, a new derivedStateOf computation is created every recomposition, defeating the purpose

Strong answer tip:

  • derivedStateOf is NOT free โ€” it adds computation overhead; only use it when input state changes much more frequently than the output would change

๐Ÿš€ See Full Deep Dive


Explain Compose accessibility and semantics correctness for TalkBack and focus order

intermediate compose accessibility talkback semantics
View Answer

Compose builds an accessibility semantics tree parallel to the composition tree; TalkBack reads from the semantics tree, so incorrect or missing semantics cause silent accessibility failures.

In interviews, cover:

  • Modifier.semantics { contentDescription = "..." } sets what TalkBack announces; without it, composables with no text content are silent or read their raw structure

  • clickable {} automatically adds Role.Button semantics unless overridden; ensure custom interactive composables include role and stateDescription

  • mergeDescendants = true on a parent semantics block collapses a card's children into one accessibility node โ€” essential for list items with multiple interactive elements

  • focus traversal: use Modifier.focusRequester() and FocusManager.moveFocus(FocusDirection.Next) to control keyboard/D-pad navigation order; default order follows layout but can diverge in RTL or multi-column layouts

Strong answer tip:

  • test with TalkBack enabled and a physical device; the Compose semantics inspector in Android Studio shows the accessibility tree but does not fully simulate real TalkBack exploration behavior

๐Ÿš€ See Full Deep Dive


Debug - snackbar, toast, or navigation event triggers twice in Compose

intermediate compose debugging events effects
View Answer

This is almost always caused by collecting a one-shot event from StateFlow (which replays to new collectors) or a LaunchedEffect with the wrong key (causing restart on recomposition).

In interviews, cover:

  • root cause A: event in StateFlow โ€” when the composable recomposes (reattachment, rotation), the new collector sees the last emitted value and acts on it again; fix: use SharedFlow(replay=0) or clear the state after consuming it

  • root cause B: LaunchedEffect(someState) โ€” if someState changes during the operation (e.g. loading becomes done), the effect cancels and relaunches, triggering the action twice; fix: key the effect on a stable trigger like a UUID or use a Channel

  • root cause C: Composable in a NavBackStackEntry is briefly composed twice during transitions; guard events with a lifecycle check using Lifecycle.currentState.isAtLeast(RESUMED)

Strong answer tip:

  • add logging at the point of event emission and collection to count invocations; use Layout Inspector + Recomposition Counts to see which composable is re-entering

๐Ÿš€ See Full Deep Dive


Debug - LaunchedEffect re-runs unexpectedly on recomposition

intermediate compose debugging launched-effect recomposition
View Answer

LaunchedEffect re-runs when its key changes; unexpected re-runs mean an unstable value is being used as the key.

In interviews, cover:

  • LaunchedEffect(someObject) where someObject is a new instance every recomposition (e.g. a lambda, a non-stable data class) causes the effect to cancel and restart each time

  • fix: use a stable key โ€” Unit (run once), viewModel (stable instance), a primitive ID, or a remember { } value

  • Lambda captures: if the effect body captures a var or an unstable object, and you use rememberUpdatedState incorrectly, state inside the effect can become stale; use rememberUpdatedState for values that should update without restarting the effect

  • derived keys: if the key is computed from multiple derived states, wrap it in a stable holder or derive a primitive (e.g. the item ID integer rather than the item object)

Strong answer tip:

  • use Compose Compiler Metrics (./gradlew assembleDebug -PcomposeCompilerMetrics) to identify unstable classes being passed as keys

๐Ÿš€ See Full Deep Dive


Debug - list item state jumps between rows in LazyColumn

intermediate compose debugging lazy-list keys state
View Answer

LazyColumn recycles composables across items by position; without stable keys, local state (checkboxes, expansion) transfers to the wrong item when the list scrolls.

In interviews, cover:

  • default behavior: LazyColumn keys items by index; when an item scrolls off-screen, its composable slot is reused for a new item at the same position โ€” any remembered state from the old item survives

  • fix: provide stable keys via items(list, key = { it.id }) { ... }; this allows Compose to correctly associate remembered state with the logical item identity, not its position

  • animated item placement also requires keys: without them, insertions and deletions look like mutations of existing items rather than smooth additions/removals

  • avoid using index as key: if items are reordered or inserted, index-based keys are effectively unstable

Strong answer tip:

  • keys must be stable across recompositions AND unique within the list; if two items share a key, Compose will throw an IllegalArgumentException in debug builds

๐Ÿš€ See Full Deep Dive


Debug - excessive recompositions while typing in a TextField

intermediate compose debugging textfield recomposition state
View Answer

TextField with an unstable onValueChange lambda or state read inside a parent composable causes the entire tree above the field to recompose on every keystroke.

In interviews, cover:

  • root cause: if onValueChange = { viewModel.onInput(it) } is defined inline in a composable that isn't skippable, the lambda is a new object every recomposition โ€” this makes TextField's parameter comparison fail and triggers recomposition of the parent

  • fix A: hoist TextField state into a ViewModel; expose a single StateFlow for the text value and collect it; the parent composable becomes stable because it only depends on the collected string

  • fix B: wrap the lambda with rememberUpdatedState or use a @Stable callback holder

  • fix C: ensure the parent composable receives stable parameters so it is skippable โ€” use the Compose compiler metrics report to confirm it is marked as skippable

Strong answer tip:

  • reproduce with Layout Inspector Recomposition Counts; counts incrementing by 1 per keystroke at the TextField level is expected; counts at multiple parent levels indicate the parent is incorrectly non-skippable

๐Ÿš€ See Full Deep Dive


Debug and fix slow scrolling and jank in Compose lazy lists

advanced compose debugging performance jank lazy-list
View Answer

Jank in LazyColumn/LazyRow during scrolling is almost always caused by expensive work during item composition, unstable item types, or image loading on the main thread.

In interviews, cover:

  • expensive item composition: if itemContent creates complex View trees, performs non-trivial calculations, or loads data synchronously during composition, the main thread frame budget (16ms at 60fps) is exceeded

  • fix: move any non-trivial data derivation to ViewModel or a background thread; only read pre-computed values in the composable

  • unstable item types: if the Item class is not considered stable by the Compose compiler (has mutable fields or non-stable type parameters), the compiler cannot skip recomposition on scroll โ€” annotate with @Stable or use immutable data classes with stable types only

  • image loading: ensure Coil/Glide is used with AsyncImage which loads off the main thread; never load bitmaps synchronously in composition

  • item height: if item height is not known, Compose must measure during scroll; provide fixed height via Modifier.height() or .fillParentMaxHeight() where possible

Strong answer tip:

  • profile with System Tracing (Perfetto) and look for long slices on the Main thread labeled with composable names โ€” this identifies the expensive items precisely

๐Ÿš€ See Full Deep Dive


Explain Paging 3 integration with Compose and correct invalidation control

advanced compose paging flow invalidation
View Answer

Paging 3 exposes Flow> which must be collected as LazyPagingItems using collectAsLazyPagingItems() โ€” direct collection with collectAsState() breaks paging internals.

In interviews, cover:

  • val items = pager.flow.collectAsLazyPagingItems(): this correctly hooks into Paging 3's internal load state tracking and triggers prefetch at correct thresholds

  • LazyColumn { items(items) { item -> ... } } combines with itemKey { it.id } to maintain stable identity across page loads and list animations

  • invalidation: call items.refresh() to trigger full invalidation; partial invalidation from Room is automatic when the backing source table changes and the PagingSource emits a new snapshot

  • load state: items.loadState.refresh, .append, .prepend expose loading/error/done states; display shimmer or error UI based on these, not an external loading flag

  • avoid collecting PagingData as a raw list โ€” PagingData is a one-shot token; re-collecting it causes duplicate content or crashes

Strong answer tip:

  • cachedIn(viewModelScope) on the flow prevents new page loads on every recomposition or screen rotation โ€” always apply it in the ViewModel before exposing the flow to the UI

๐Ÿš€ See Full Deep Dive


Explain Compose Modifier chains - ordering, performance cost, and correctness

intermediate compose modifier layout performance
View Answer

Modifier chains in Compose are order-dependent; the sequence of modifiers determines both correctness and the cost incurred in layout and draw passes.

In interviews, cover:

  • Modifier.padding().clickable() vs Modifier.clickable().padding(): padding first means the click target is smaller (padding is outside clickable area); clickable first means the expanded padded area is clickable โ€” this is a layout vs input behavior difference

  • draw modifiers (background, border) are applied in chain order; background().padding() draws background then adds padding inside it; padding().background() draws the background inside the padding

  • each Modifier adds a LayoutNode; long chains add traversal cost in measure and layout passes; prefer combining composites over chaining many individual modifiers when layout performance is critical

  • Modifier.then() merges two modifier chains; use foldIn/foldOut for custom modifier extensions that must preserve order

Strong answer tip:

  • a common composability bug: writing extension modifiers that assume a fixed internal ordering of layout vs semantics modifiers; use Modifier.Node API (the new stable API from Compose 1.3+) for correct low-level modifier implementation

๐Ÿš€ See Full Deep Dive