contentType in LazyColumn: Mixed List Performance

Use contentType in a LazyColumn when a feed contains genuinely different reusable row structures—such as message rows, date separators, promotions, and loading placeholders. Return one small, stable type for each layout shape. Compose can then reuse compositions only among compatible items instead of attempting to reuse a composition for unrelated content.
The official lazy lists and grids guide says contentType helps Compose reuse compositions between items of the same type, maximizing reuse in mixed lazy layouts. It is the Compose equivalent of describing compatible view types, but it does not replace stable item keys.
key versus contentType
| Parameter | Answers | Example |
|---|---|---|
key | Which exact data item is this? | message.id |
contentType | Which item layout is compatible with this one? | FeedKind.Message |
A key preserves identity when items move. A content type describes reusable structure. Dynamic feeds frequently need both.
Model the mixed feed explicitly
Prepare a display model in the presentation layer. The composable should render that state and forward events, rather than deciding which business data becomes a promotion or separator.
sealed interface FeedItemUi {
val id: String
data class Article(
override val id: String,
val title: String
) : FeedItemUi
data class Ad(
override val id: String,
val label: String
) : FeedItemUi
data class Divider(
override val id: String,
val dateLabel: String
) : FeedItemUi
}Supply a type for each layout shape
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
@Composable
fun Feed(
items: List<FeedItemUi>,
onArticleClick: (String) -> Unit
) {
LazyColumn {
items(
items = items,
key = { item -> item.id },
contentType = { item ->
when (item) {
is FeedItemUi.Article -> FeedKind.Article
is FeedItemUi.Ad -> FeedKind.Ad
is FeedItemUi.Divider -> FeedKind.Divider
}
}
) { item ->
when (item) {
is FeedItemUi.Article -> ArticleRow(
article = item,
onClick = { onArticleClick(item.id) }
)
is FeedItemUi.Ad -> AdRow(ad = item)
is FeedItemUi.Divider -> DateDivider(label = item.dateLabel)
}
}
}
}
private enum class FeedKind { Article, Ad, Divider }The return value does not need to be elaborate. An enum, sealed type, or short string is usually enough. It should remain stable and describe the composable structure, not a value that changes for every item.
When contentType helps
Use it for a list where the row tree differs materially: an image card versus a text message, a full-width error panel, or a section divider. The Android documentation gives this advice specifically for lists and grids with multiple content types.
Do not add it mechanically to a uniform list. If every row calls the same composable with the same structure, null—the default compatible type—is sufficient. Creating a unique content type for every ID prevents useful reuse.
Common mistakes
Returning the item’s ID
An ID is a key, not a type. contentType = { it.id } says every item has a different layout and defeats the reuse it is meant to enable.
Returning a mutable display property
Do not return text, a selected state, or a changing timestamp. A content type should change only when the item’s composable structure changes.
Treating contentType as a performance guarantee
It enables compatible composition reuse. It does not make expensive image decoding, blocking work, or a complex row automatically fast. Measure actual scrolling in a release build, as the official guide recommends, and use the recomposition performance guide to investigate symptoms.
Forgetting stable keys
contentType does not preserve item identity on reorder. Keep a stable key for data that can move or be refreshed; see Lazy list keys.
Test a mixed feed
Test all kinds of rows, then insert, remove, and reorder data while the user is scrolled into the feed. Confirm an article row never renders with promotion state, and confirm the real list works at large font sizes and in dark theme. Use profile measurements before and after a change rather than relying on a generic performance claim.
FAQ
Does every LazyColumn need contentType?
No. It is useful when the collection has distinct reusable layout shapes. A uniform list normally does not need it.
Can contentType be a string?
Yes, provided it is small and stable. An enum or sealed type often makes the set of compatible layouts clearer.
Is contentType the same as a RecyclerView view type?
It serves a similar purpose: it tells the lazy layout which item compositions are compatible for reuse. Compose still uses composables rather than View holders.