LazyColumn and LazyRow
Why Lazy?
Rendering thousands of items at once would consume excessive memory. Lazy lists only compose and lay out items that are visible on screen, recycling compositions as the user scrolls.
LazyColumn
LazyColumn is the Compose equivalent of RecyclerView. It vertically scrolls through a list of items.
@Composable
fun MessageList(messages: List<Message>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = messages,
key = { it.id }
) { message ->
MessageRow(message = message)
}
}
}
@Composable
fun MessageRow(message: Message) {
Card(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.padding(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
UserAvatar(url = message.senderAvatar)
Column {
Text(text = message.sender, style = MaterialTheme.typography.titleSmall)
Text(text = message.text, style = MaterialTheme.typography.bodyMedium)
}
}
}
}
Key Parameter
The key parameter tells Compose how to identify items uniquely. Without keys, Compose uses position, which causes unnecessary recompositions when items are reordered or inserted.
// BAD: No key, position-based identity
items(messages) { message ->
MessageRow(message)
}
// GOOD: Stable identity via unique ID
items(messages, key = { it.id }) { message ->
MessageRow(message)
}
LazyRow
LazyRow scrolls horizontally:
@Composable
fun CategoryCarousel(categories: List<Category>) {
LazyRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(categories, key = { it.id }) { category ->
CategoryChip(
category = category,
onClick = { /* handle click */ }
)
}
}
}
contentType
contentType helps Compose reuse compositions across items of the same type:
LazyColumn {
items(
items = feedItems,
key = { it.id },
contentType = { it.type } // "header", "post", "ad"
) { item ->
when (item.type) {
"header" -> FeedHeader(item as HeaderItem)
"post" -> FeedPost(item as PostItem)
"ad" -> AdBanner(item as AdItem)
}
}
}
Items with the same contentType can share compositions, reducing allocation overhead.
LazyVerticalGrid and Staggered Grids
LazyVerticalGrid
For grid layouts, use LazyVerticalGrid:
@Composable
fun ProductGrid(products: List<Product>) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(products, key = { it.id }) { product ->
ProductCard(product = product)
}
}
}
GridCells
GridCells controls how columns are sized:
// Fixed number of columns
GridCells.Fixed(3)
// Adaptive: minimum width per cell, auto-fill columns
GridCells.Adaptive(minSize = 150.dp)
// Fixed with spacing
GridCells.Fixed(2) // Combined with horizontalArrangement for gaps
Adaptive is useful for responsive layouts that adjust column count based on screen width.
Staggered Grid
For masonry-style layouts where items have varying heights:
@Composable
fun PinterestLayout(items: List<PinItem>) {
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalItemSpacing = 8.dp
) {
items(items, key = { it.id }) { item ->
PinCard(item = item)
}
}
}
Headers and Footers
Add items before and after the main list:
LazyColumn {
// Header
item {
Text(
text = "Messages",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(16.dp)
)
}
// Main list
items(messages, key = { it.id }) { message ->
MessageRow(message)
}
// Footer
item {
Text(
text = "End of messages",
modifier = Modifier.padding(16.dp),
style = MaterialTheme.typography.bodySmall
)
}
}
Performance Tips
- Always provide
keyfor stable item identity. - Use
contentTypewhen items have different compositions. - Avoid creating lambdas or objects inside item lambdas.
- Use
derivedStateOffor scroll-position-based UI (e.g., FAB visibility). - Keep item composables small to minimize recomposition scope.
Quiz
1. Why should you provide a key parameter to LazyColumn items?
2. What is the difference between GridCells.Fixed and GridCells.Adaptive?
3. What does contentType do in lazy list items?
4. How do you add a header above a LazyColumn list?
Flashcards
Question
What is the Compose equivalent of RecyclerView?
Click to reveal answer
Answer
LazyColumn (vertical) and LazyRow (horizontal). They compose only visible items, similar to RecyclerView but without adapters or ViewHolders.
Question
When should you use contentType in LazyColumn?
Click to reveal answer
Answer
When items have different composable structures (e.g., headers, posts, ads). It enables Compose to reuse compositions across items of the same type.
Question
What does GridCells.Adaptive do?
Click to reveal answer
Answer
Automatically calculates the number of columns based on available width and a minimum cell size, making the grid responsive to different screen sizes.
Question
What is LazyVerticalStaggeredGrid used for?
Click to reveal answer
Answer
Masonry-style layouts where items have varying heights, like Pinterest. Items fill available vertical space in their column.
Revision Notes
Key Takeaways
- 1. LazyColumn and LazyRow only compose visible items for efficient scrolling
- 2. Always provide a key parameter for stable item identity during reordering
- 3. contentType enables composition reuse across items with the same structure
- 4. GridCells.Adaptive makes grids responsive to different screen sizes
- 5. item {} blocks add headers and footers within lazy lists
Interview Tips
- • Explain why keys matter: without them, Compose uses position-based identity which causes unnecessary recompositions
- • Discuss the difference between LazyColumn and RecyclerView (adapter/ViewHolder vs composable-based)
- • Know when to use contentType for performance optimization
- • Be ready to design a responsive grid layout with GridCells.Adaptive
Cheat Sheet
Lazy Lists and Grids Cheat Sheet
LazyColumn:
- Vertical scrollable list, compose-only visible items
- items(list, key = { it.id }, contentType = { ... })
- contentPadding and verticalArrangement for spacing
LazyRow:
- Horizontal scrollable list, same API as LazyColumn
LazyVerticalGrid:
- columns = GridCells.Fixed(n) or GridCells.Adaptive(minSize)
- Same items/key/contentType API
LazyVerticalStaggeredGrid:
- Masonry layout with varying item heights
- StaggeredGridCells.Fixed(n) or .Adaptive
Headers/Footers:
- item { } blocks before/after items()
Performance:
- Always provide key for stable identity
- Use contentType for mixed item types
- Keep item composables small