
Kotlin Flow combine zip flatMapLatest는 이름만 보면 모두 여러 흐름을 합치는 연산자처럼 보입니다. 하지만 실제로는 방출 타이밍과 취소 방식이 다릅니다.
combine은 최신 값을 계속 조합하고, zip은 순서대로 한 쌍씩 묶고, flatMapLatest는 새 입력이 오면 이전 작업을 버립니다. 이 차이를 모르면 UI 상태가 늦게 바뀌거나, 불필요한 네트워크 요청이 계속 살아남을 수 있습니다.
이 글은 API 목록을 외우는 대신 Android 화면 상태, 1:1 매칭, 검색 요청 취소 예제로 언제 무엇을 써야 하는지 정리합니다.
Kotlin Flow combine zip flatMapLatest를 먼저 한 장으로 보기

combine은 최신 상태를 합칠 때 쓴다
combine은 여러 Flow의 가장 최근 값을 사용합니다. 한쪽 값만 새로 나와도 다른 쪽의 최신 값을 함께 사용해 새 결과를 만듭니다.
그래서 화면 상태를 만들 때 자주 맞습니다. 사용자의 로그인 상태, 필터 값, 서버 데이터가 각각 따로 바뀌더라도 화면은 항상 최신 조합으로 다시 만들어져야 하기 때문입니다.
val uiState: Flow<ProductUiState> =
combine(productsFlow, selectedFilterFlow, userFlow) { products, filter, user ->
ProductUiState(
products = products.filterBy(filter),
canBuy = user != null
)
}여기서 필터만 바뀌어도 최신 상품 목록과 최신 사용자 상태가 함께 반영됩니다. 이것이 combine의 핵심입니다.
zip은 순서대로 한 쌍씩 묶을 때 쓴다
zip은 두 Flow에서 나온 값을 순서대로 한 쌍씩 묶습니다. 공식 문서 예시처럼 첫 번째 값끼리, 두 번째 값끼리 연결됩니다. 한쪽 Flow가 끝나면 결과도 끝납니다.
이 특성 때문에 zip은 UI 상태 조합보다는 1:1 대응이 분명한 작업에 어울립니다. 예를 들어 로컬 샘플 데이터와 원격 응답을 순서대로 비교하거나, 테스트에서 두 이벤트 흐름을 맞춰 볼 때입니다.
val paired: Flow<String> =
numbers.zip(labels) { number, label ->
"$number:$label"
}zip을 화면 상태에 습관적으로 쓰면 한쪽 Flow가 새 값을 더 자주 내보낼 때 기대와 다른 결과가 나올 수 있습니다. 최신 상태 반영이 목적이면 대부분 combine을 먼저 의심하는 편이 안전합니다.
flatMapLatest는 이전 작업을 취소할 때 쓴다
flatMapLatest는 원본 Flow가 새 값을 방출할 때마다 새 Flow로 갈아탑니다. 그리고 이전 transform에서 만들어진 Flow는 취소됩니다.
대표적인 예시는 검색입니다. 사용자가 검색어를 빠르게 바꾸면 이전 검색 요청 결과는 더 이상 화면에 보여주면 안 됩니다. 이때 flatMapLatest가 자연스럽습니다.
val searchResults: Flow<List<SearchItem>> =
queryFlow
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { query ->
repository.search(query)
}여기서 사용자가 `ko`, `kot`, `kotlin`을 빠르게 입력하면 이전 query의 검색 Flow는 취소되고 최신 query 결과만 살아남습니다.
flatMapLatest의 핵심은 합치기가 아니라 전환입니다. 새 입력이 오면 이전 작업을 계속 기다리지 않는다는 점이 중요합니다.
헷갈릴 때 보는 질문
- 각 Flow의 최신 값을 계속 반영해야 하는가? 그러면 combine을 먼저 본다.
- 두 Flow의 값을 순서대로 한 쌍씩 소비해야 하는가? 그러면 zip을 본다.
- 새 입력이 오면 이전 비동기 작업이 의미 없어지는가? 그러면 flatMapLatest를 본다.
- 이전 결과가 늦게 도착하면 화면을 오염시킬 수 있는가? flatMapLatest가 필요할 가능성이 높다.
Android UI에서 자주 나오는 패턴
ViewModel에서는 여러 상태를 combine한 뒤 stateIn으로 UI 상태를 만들고, 사용자 검색이나 상세 조회처럼 입력이 바뀌면 이전 요청을 취소해야 하는 구간에는 flatMapLatest를 쓰는 조합이 자주 등장합니다.
val uiState = combine(
searchResults,
selectedSort,
favoriteIds,
) { results, sort, favorites ->
SearchUiState(
items = results.sortedBy(sort).markFavorites(favorites),
sort = sort,
)
}이 예시에서 검색 결과 자체는 flatMapLatest로 만들 수 있고, 검색 결과와 정렬 값, 즐겨찾기 목록을 화면 상태로 합칠 때는 combine을 사용할 수 있습니다.
정리
Kotlin Flow combine zip flatMapLatest 차이는 결과 타입보다 시간 감각으로 이해해야 합니다. combine은 최신 값, zip은 순서쌍, flatMapLatest는 최신 작업으로의 전환입니다.
화면 상태가 이상하게 늦게 바뀌거나, 예전 검색 결과가 갑자기 화면에 나타난다면 연산자 선택을 다시 봐야 합니다. 특히 Android UI에서는 combine과 flatMapLatest를 함께 쓰는 구조가 많습니다.
함께 보면 좋은 글: callbackFlow는 언제 써야 할까, stateIn과 shareIn은 언제 써야 할까
출처: Kotlin Flow combine, Kotlin Flow zip, Kotlin Flow flatMapLatest