Skip to content
intermediate Phase 4 · Jetpack Compose

Lazy Lists & Grids

Display scrollable lists and grids with LazyColumn, LazyRow, and LazyVerticalGrid.

40m
2 problems
Topic Progress 0%

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 key for stable item identity.
  • Use contentType when items have different compositions.
  • Avoid creating lambdas or objects inside item lambdas.
  • Use derivedStateOf for 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?

Question 1 options

2. What is the difference between GridCells.Fixed and GridCells.Adaptive?

Question 2 options

3. What does contentType do in lazy list items?

Question 3 options

4. How do you add a header above a LazyColumn list?

Question 4 options

Flashcards

Question

What is the Compose equivalent of RecyclerView?

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?

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?

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?

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