Interview QuestionPractical QuestionFollow-up Questions

Stateful vs. Stateless Composables

skydovesJaewoong Eum (skydoves)||9 min read

Stateful vs. Stateless Composables

In Jetpack Compose, composables can be categorized as stateful or stateless depending on whether they manage their own state internally or receive it from an external source. Understanding this distinction is fundamental to building reusable, testable, and scalable UI components. By the end of this lesson, you will be able to:

  • Define the difference between stateful and stateless composables.
  • Explain how state hoisting converts a stateful composable into a stateless one.
  • Identify when each type is appropriate in a real application.
  • Describe how the stateless pattern improves testability and reusability.

Stateful Composables

A stateful composable creates and manages its own state using functions like remember and mutableStateOf. It encapsulates both the UI rendering logic and the state logic within the same composable function, making it self contained but tightly coupled.

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

In this example, the Counter composable owns the count state. No external caller can read or modify it. This makes the composable simple to use but limits its flexibility. You cannot share this state with a sibling composable, persist it in a ViewModel, or write a unit test that exercises different count values without rendering the composable.

Stateful composables are suitable for isolated, self contained UI elements where the state does not need to be observed or modified from the outside. Common examples include a password visibility toggle, a dropdown expanded state, or an animation controller that no other composable needs to know about.

A key drawback is that the state is lost when the composable leaves the composition tree. If you need the state to survive beyond the composable's lifetime, such as across navigation events or process death, it must be hoisted into a ViewModel or saved state mechanism.

Stateless Composables

A stateless composable does not hold any state. It receives the current state as a parameter and communicates user interactions back to the caller through callback lambdas. All state management is handled externally.

@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) {
        Text("Count: $count")
    }
}

Here, the composable renders whatever count value it receives and delegates the click action to onIncrement. The caller decides how state is stored, updated, and shared. This separation makes the composable reusable in any context that provides an integer and a click handler.

Stateless composables follow the same principle as pure functions: given the same inputs, they always produce the same output. This makes them straightforward to test using Compose testing APIs, since you can pass arbitrary values and verify the rendered output without setting up any state management infrastructure. It also makes them easy to preview in Android Studio by supplying sample data directly in the @Preview annotation.

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor