@Stable vs @Immutable: When to Use Which
A lot of Compose code has these two annotations sprinkled around almost at random, as if they were interchangeable spelling variants of…
@Stable vs @Immutable: When to Use Which
A lot of Compose code has these two annotations sprinkled around almost at random, as if they were interchangeable spelling variants of “please make this faster.” They aren’t. Each one is a specific promise you’re making to the compiler, and picking the wrong one, or making a promise you can’t keep, leads to bugs that are painful to track down.

They’re promises, not optimizations
Neither annotation makes your code faster on its own. What they do is hand the Compose compiler a guarantee it couldn’t figure out by itself, so it can start skipping recompositions it would otherwise play safe and run. The speed comes from the skipping. The annotation is just you signing a contract that makes the skipping safe.
That framing matters because if you break the contract, the compiler doesn’t catch you. It believes you, skips a recomposition it shouldn’t have, and your screen shows old data. Nothing crashes, nothing warns, and you spend an afternoon wondering why your UI is stale.
@Immutable: nothing changes after construction
@Immutable is the stronger promise. You're telling the compiler that once an instance is built, none of its public properties will ever change. Not through a setter, not through a backing collection, not in any way the outside world could observe.
@Immutable
data class Article(
val title: String,
val body: String,
val tags: List<String>
)
That List would normally make the class unstable, because the compiler can't see whether the underlying implementation is mutable. The annotation overrides that doubt. You're vouching that the list handed in never gets mutated after this Article exists, so the compiler can treat the whole thing as a fixed value. Use this for your UI state models and anything you build once and only ever replace wholesale.
@Stable: it can change, but it always tells
@Stable is the looser promise, for types that do change over time but are well-behaved about it. You're guaranteeing two things. The first is that whenever a public property changes, the change happens through Compose's own state system, so a recomposition gets triggered. The second is that equals stays consistent for the same pair of instances.
A class built around mutableStateOf is the classic fit:
@Stable
class FormState {
var email by mutableStateOf("")
var isValid by mutableStateOf(false)
}
This object absolutely changes, so @Immutable would be a lie. But every change flows through mutableStateOf, which means Compose always finds out. That's exactly what the contract @Stable describes, and it lets the compiler treat the type as something it can reason about.
How to choose in one question
Ask whether the object can ever change after you create it. If the honest answer is no, go for @Immutable. If it can change but only ever through a Compose state that notifies on every update, use @Stable. And if it changes in ways Compose can't see, like a plain var you reassign yourself, then neither annotation applies, and slapping one on is the beginning of your bug.
Most of the time, you’ll want @Immutable, because most of the data you pass into composables is UI state you replace rather than mutate. Keep @Stable for the handful of holder classes that really do carry changing state(s). And whichever you pick, only promise what you can actually keep, because the compiler is going to take you at your word.
May your immutables never mutate and your stables always notify.
메타데이터
- post_id
- 6e776b7e078b
- slug
- stable-vs-immutable-when-to-use-which-6e776b7e078b
- url
- https://medium.com/@christophybarth/stable-vs-immutable-when-to-use-which-6e776b7e078b
- canonical_url
- https://medium.com/@christophybarth/stable-vs-immutable-when-to-use-which-6e776b7e078b
- author_url
- https://medium.com/@christophybarth
- status
- ok
- fetched_at
- 2026-06-24 11:06:28