---
id: android-lazylist-performance
title: LazyList Performance — key / contentType / 측정
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [android, compose, lazy-list, performance, vibe-coding]
tech_stack: { language: "Kotlin / Compose", applicable_to: ["Android"] }
applied_in: []
aliases: [LazyColumn, LazyRow, key, contentType, recomposition, animateItem]
---

# LazyList Performance

> RecyclerView 의 Compose 버전. **key + contentType 가 핵심**. Stable / immutable 데이터, item 주변 modifier 안에 너무 많은 람다 X.

## 📖 핵심 개념
- key: item 식별 — recompose 시 재사용 / animation.
- contentType: 같은 type item recycle.
- LazyListState: scroll position / 동기 제어.
- Stable: data class 가 immutable + Stable.

## 💻 코드 패턴

### Basic
```kotlin
LazyColumn {
    items(
        items = users,
        key = { it.id },
        contentType = { it.kind }, // 'header' | 'row'
    ) { user ->
        UserRow(user)
    }
}
```

### key 의 효과
```kotlin
// key 없음: 위치 기반 — list 변경 시 다른 item recompose
items(users) { user -> UserRow(user) }

// key 있음: id 기반 — list 변경 시 같은 id 면 재사용
items(users, key = { it.id }) { user -> UserRow(user) }
```

### contentType — 다른 type item 재사용
```kotlin
LazyColumn {
    items(items, key = { it.id }, contentType = {
        when (it) {
            is HeaderItem -> "header"
            is UserItem -> "user"
            is AdItem -> "ad"
        }
    }) { item ->
        when (item) {
            is HeaderItem -> Header(item)
            is UserItem -> UserRow(item)
            is AdItem -> AdRow(item)
        }
    }
}
```

### Stable / Immutable annotation
```kotlin
@Immutable
data class User(val id: String, val name: String, val email: String)

// 또는
@Stable
class UserList(val users: List<User>)
```

→ Compose 가 skip 가능 판단.

### State hoisting + remember
```kotlin
@Composable
fun UserRow(user: User, onClick: (String) -> Unit) {
    // ❌람다 매번 새 — recompose
    // Row(modifier = Modifier.clickable { onClick(user.id) })

    // ✅ remember
    val click = remember(user.id, onClick) { { onClick(user.id) } }
    Row(modifier = Modifier.clickable(onClick = click)) { ... }
}
```

### LazyListState 제어
```kotlin
val state = rememberLazyListState()

LazyColumn(state = state) { ... }

// scroll
LaunchedEffect(targetIndex) { state.animateScrollToItem(targetIndex) }

// 보이는 item
val visibleItems = remember { derivedStateOf { state.layoutInfo.visibleItemsInfo } }

// 끝에 도달
val endReached by remember { derivedStateOf {
    val last = state.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
    last >= state.layoutInfo.totalItemsCount - 5
} }
LaunchedEffect(endReached) { if (endReached) loadMore() }
```

### animateItem (Compose 1.7+)
```kotlin
items(users, key = { it.id }) { user ->
    UserRow(user, modifier = Modifier.animateItem())
}
```

→ insert / remove / move 시 자동 animation.

### Sticky header
```kotlin
LazyColumn {
    sections.forEach { section ->
        stickyHeader(key = "header-${section.id}") {
            HeaderRow(section)
        }
        items(section.items, key = { it.id }) { ItemRow(it) }
    }
}
```

### Paging 3 통합
```kotlin
val items = pager.flow.collectAsLazyPagingItems()

LazyColumn {
    items(items.itemCount, key = items.itemKey { it.id }) { idx ->
        val item = items[idx] ?: return@items
        ItemRow(item)
    }
    if (items.loadState.append is LoadState.Loading) {
        item { Spinner() }
    }
}
```

### nestedScroll + Top app bar
```kotlin
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
    topBar = { LargeTopAppBar(..., scrollBehavior = scrollBehavior) },
    modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { padding ->
    LazyColumn(contentPadding = padding) { ... }
}
```

### 측정 — Compose Compiler Metrics
```bash
# build.gradle.kts
kotlinOptions {
    freeCompilerArgs += listOf(
        "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=$buildDir/compose_metrics"
    )
}
```

→ 어떤 컴포넌트가 Stable / Skippable 분석.

### 큰 list — virtualization 자동
LazyColumn 자체가 virtualization. 단 1000+ item 도 OK.

### Performance 측정 (Macrobenchmark)
```kotlin
@Test
fun scrollPerformance() {
    benchmarkRule.measureRepeated(
        packageName = TARGET_PACKAGE,
        metrics = listOf(FrameTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.WARM,
    ) {
        startActivityAndWait()
        device.findObject(By.res("list")).fling(Direction.DOWN)
    }
}
```

## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| 단순 list | LazyColumn |
| Pagination | + Paging 3 |
| Grid | LazyVerticalGrid |
| Staggered | LazyVerticalStaggeredGrid |
| Horizontal | LazyRow |
| Mix headers + items | + stickyHeader |

## ❌ 안티패턴
- **key 없음 + 동적 list**: recompose 폭발.
- **Lambda inline 매번 새**: re-recompose. remember.
- **Modifier 안 무거운 계산**: 자식 매번. derivedStateOf.
- **state 가 unstable List<X>**: skip 안 됨. Stable 또는 ImmutableList (kotlinx.collections.immutable).
- **Image inline (URL → Coil 매번 fetch)**: cache 활용.
- **MutableList → toList()**: snapshot 매번. immutableList.
- **Item 안 own scroll**: 부모와 충돌.

## 🤖 LLM 활용 힌트
- key + contentType + Stable data + animateItem 4종.
- Lambda = remember.
- Compose Compiler Metrics 로 측정.

## 🔗 관련 문서
- [[Android_Compose_Recomposition_Pitfalls]]
- [[Android_Compose_Custom_Layout]]
- [[Android_Paging_3_Patterns]]
