LazyColumn Performance Masterclass: Handling 10,000+ Items Smoothly

By Engineering Team • Published: 2025-12-10 • Updated: 2025-12-1016 min read

LazyColumnPerformanceListsOptimization

The Foundation: Keys Are Non-Negotiable

The single most important optimization for LazyColumn is providing stable keys. Without keys, Compose cannot efficiently track item identity across data changes, leading to unnecessary recompositions and visual glitches.

// ❌ BAD: No keys - Compose uses index by default
LazyColumn {
    items(users) { user ->
        UserCard(user)
    }
}

// ✅ GOOD: Stable keys based on unique identifier
LazyColumn {
    items(
        items = users,
        key = { user -> user.id }  // Must be unique and stable
    ) { user ->
        UserCard(user)
    }
}

Content Type Optimization

When your list contains different item types (headers, content, footers), use contentType to enable Compose to reuse compositions more efficiently:

LazyColumn {
    items(
        items = feedItems,
        key = { it.id },
        contentType = { item ->
            when (item) {
                is FeedItem.Header -> "header"
                is FeedItem.Post -> "post"
                is FeedItem.Ad -> "ad"
            }
        }
    ) { item ->
        when (item) {
            is FeedItem.Header -> HeaderCard(item)
            is FeedItem.Post -> PostCard(item)
            is FeedItem.Ad -> AdCard(item)
        }
    }
}

Avoid Expensive Operations in Item Scope

Item composables should be as lightweight as possible. Move expensive calculations outside the LazyColumn scope:

  • Use remember with proper keys for computed values
  • Avoid creating new objects (lambdas, data classes) inside items
  • Pre-process data in ViewModel before passing to UI
  • Use derivedStateOf for filtered/sorted lists

Prefetch and Placeholder Strategies

For extremely large lists or network-loaded content, implement prefetching and placeholders:

@Composable
fun InfiniteUserList(
    users: LazyPagingItems<User>
) {
    LazyColumn {
        items(
            count = users.itemCount,
            key = users.itemKey { it.id }
        ) { index ->
            val user = users[index]
            if (user != null) {
                UserCard(user)
            } else {
                UserCardPlaceholder()  // Shimmer loading state
            }
        }
    }
}
Note: Use Paging 3 library with Compose for automatic prefetching and memory management. It handles the complex logic of loading data in chunks.
← Back to Blog