What you'll learn
Almost every app shows a list — chats, contacts, a feed, search results. Rendering those efficiently is a solved problem in Compose, but only if you reach for the right tool. This module is about the lazy list composables and the one detail (keys) that keeps them fast and correct.
By the end you'll be able to:
- Explain why
LazyColumnbeats a scrollingColumnfor real data - Build vertical and horizontal lists
- Use
item,items, anditemsIndexed - Add keys for correctness and performance, and build grids
Why lazy lists
A regular Column composes all its children immediately, whether they're on screen or not. For ten items that's fine; for a thousand it's a disaster — slow startup and wasted memory:
// DON'T do this for long or dynamic lists:
Column(Modifier.verticalScroll(rememberScrollState())) {
items.forEach { item ->
ItemRow(item) // every item is composed up front — slow, memory-hungry
}
}A lazy list only composes the items that are currently visible (plus a small buffer), and recycles them as you scroll — the same idea as RecyclerView, but with almost no boilerplate. Use a scrolling Column only for a handful of known items; reach for LazyColumn for anything long or data-driven.
LazyColumn & LazyRow
LazyColumn scrolls vertically and LazyRow horizontally. Instead of writing children directly, you describe content inside a special scope using items:
LazyColumn {
items(contacts) { contact ->
ContactRow(contact) // composed only when scrolled into view
}
}Tip
contentPadding = PaddingValues(16.dp) and space items with verticalArrangement = Arrangement.spacedBy(8.dp) — both are parameters on the lazy list itself, so you don't pad each row by hand.item, items & itemsIndexed
Inside a lazy list you have three building blocks. Use item { } for a single one-off (a header), items(list) to emit one composable per element, and itemsIndexed(list) when you also need each element's position:
LazyColumn {
item { // a single, one-off item
Text("Contacts", style = MaterialTheme.typography.headlineSmall)
}
items(contacts) { contact -> // many items from a list
ContactRow(contact)
}
itemsIndexed(contacts) { index, contact -> // when you need the position
Text("${index + 1}. ${contact.name}")
}
}Keys — the detail that matters
By default Compose identifies items by their position in the list. That breaks down when the list changes — insert an item at the top and every row shifts by one, so Compose thinks every row changed and needlessly recomposes them (and any per-item state, like an expanded card, jumps to the wrong row). Give each item a stable, unique key and Compose can track items by identity across changes:
LazyColumn {
items(
items = contacts,
key = { contact -> contact.id }, // stable, unique identity per item
) { contact ->
ContactRow(contact)
}
}Key idea
key for lists that can change — reorder, insert, delete, or filter. Use something truly unique and stable, like a database id. Never use the list index as the key; that defeats the entire purpose.Grids
For a gallery-style grid, use LazyVerticalGrid (or LazyHorizontalGrid). You choose the columns with GridCells.Fixed(n) for a set count, or GridCells.Adaptive(minSize) to let Compose fit as many columns as will fit the width — great for responsive layouts across phone and tablet:
LazyVerticalGrid(
columns = GridCells.Fixed(2), // or GridCells.Adaptive(minSize = 120.dp)
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(photos) { photo -> PhotoCard(photo) }
}Recap & quick check
Key takeaways
- LazyColumn/LazyRow compose only visible items and recycle them — use them for any long or dynamic list.
- Describe content with item {} for one-offs and items(list) / itemsIndexed(list) for collections.
- A scrolling Column is fine only for a small, fixed number of items.
- Provide a stable, unique key for changeable lists to avoid needless recomposition and misplaced state.
- LazyVerticalGrid with GridCells.Fixed(n) or Adaptive(minSize) builds responsive grids.
Quick check
1. Why prefer LazyColumn over a scrolling Column for a 1,000-item list?
2. Which emits one composable per element of a list?
3. You need each row's position number. Use…
4. What should you pass as a list item's key?
5. How do you build a responsive grid that fits as many columns as possible?
Your lists are fast and correct. Now let's make everything beautiful and consistent with Material Design 3.