# Kotlin Flow vs StateFlow vs SharedFlow: 2026 안드로이드 면접 질문 > 2026년 안드로이드 면접관이 묻는 Kotlin Flow vs StateFlow vs SharedFlow 질문을 명확한 답변, 비교 표, 실무용 코드와 함께 정리했습니다. - Published: 2026-07-01 - Updated: 2026-07-07 - Author: SharpSkill - Tags: android, kotlin, coroutines, flow, interview - Reading time: 9 min --- Kotlin Flow와 StateFlow, SharedFlow의 차이는 2026년 안드로이드 면접에서 가장 자주 나오는 주제 중 하나이며, 이 셋을 헷갈리면 코루틴 파트를 순식간에 망치기 쉽습니다. StateFlow와 SharedFlow는 모두 Flow 위에 세워진 hot 스트림이지만, 서로 다른 문제를 풀기 위해 존재합니다. 하나는 상태를 보관하고, 다른 하나는 이벤트를 방송합니다. 아래 질문들은 안드로이드 면접관이 실제로 던지는 것들이며, 정확한 답변과 실무에 바로 쓸 수 있는 코드를 함께 정리했습니다. > **20초 안에 답하기** > > `Flow`는 cold이며 컬렉터마다 프로듀서를 한 번씩 실행합니다. `StateFlow`는 hot이고 conflate되는 스트림으로 항상 하나의 현재 값을 보관하므로 UI 상태에 이상적입니다. `SharedFlow`는 초기값이 필수가 아닌 hot 스트림으로, 화면 전환이나 스낵바 같은 일회성 이벤트에 적합합니다. ## Kotlin Flow와 StateFlow, SharedFlow의 핵심 차이 StateFlow와 SharedFlow는 `SharedFlow`의 특수화된 형태이며, `SharedFlow` 자체는 hot `Flow`입니다. 면접관이 파고드는 지점은 cold와 hot의 구분, 현재 값을 유지하는지 여부, 그리고 중복 발행을 각각 어떻게 다루는지입니다. 간단한 멘탈 모델로 정리하면 `Flow`는 레시피, `StateFlow`는 하나의 가변 값, `SharedFlow`는 이벤트 버스입니다. | 속성 | Flow (cold) | StateFlow | SharedFlow | |---|---|---|---| | 온도 | Cold | Hot | Hot | | 초기값 | 없음 | 필수 | 선택 (`replay` 통해) | | 현재 값 보관 | 안 함 | 함, `.value` 통해 | 안 함 | | 중복 발행 | 함 | 안 함 (conflate + 중복 제거) | 설정 가능 | | 컬렉터 추가 시 | 매번 새 스트림 | 공유 | 공유 | | 적합한 용도 | 비동기 데이터 파이프라인 | UI 상태 | 일회성 이벤트 | [공식 Kotlin 코루틴 문서](https://kotlinlang.org/docs/flow.html)는 이 cold-by-default 동작을 `Flow`를 정의하는 핵심 성질로 다루므로, 좋은 답변은 바로 여기서 출발합니다. ## Kotlin Flow는 왜 기본적으로 cold인가? cold flow는 `collect()`가 호출되기 전까지 아무것도 하지 않으며, 컬렉터마다 프로듀서 블록을 다시 실행합니다. 같은 cold flow에 컬렉터가 둘 붙으면 독립적인 네트워크 호출이 두 번 발생합니다. 가장 많이 출제되는 개념이므로 짧은 예제로 답변을 단단히 잡아 두는 것이 좋습니다. ```kotlin // SearchRepository.kt fun searchResults(query: String): Flow> = flow { // This block runs fresh for every collector. // Nothing executes until a collector calls collect(). val results = api.search(query) // suspending network call emit(results) // pushed downstream to the collector } ``` 프로듀서가 컬렉터마다 다시 시작되기 때문에, cold flow는 필요할 때만 실행되어야 하는 데이터 파이프라인에 알맞은 기본값입니다. 문제가 되는 경우는 하나의 값을 화면 전체가 공유해야 할 때뿐이며, 바로 그 지점에서 StateFlow와 SharedFlow가 등장합니다. ## StateFlow 면접 질문: 항상 값을 발행하는 상태 `StateFlow`는 항상 값을 가지며 그 최신 값을 새 컬렉터마다 다시 재생하는 hot flow입니다. 초기값이 필수이고, 동기적으로 읽을 수 있도록 `.value`를 노출하며, 발행을 conflate합니다. 즉 빠르게 이어지는 갱신은 중간 값을 건너뛸 수 있고, 같은 값을 두 번 설정하면 equality 기준으로 중복을 제거하기 때문에 아무것도 발행되지 않습니다. 이런 성질 덕분에 `ViewModel`에서 `LiveData`를 대체하는 현대적인 도구가 됩니다. ```kotlin // SearchViewModel.kt class SearchViewModel(private val repo: SearchRepository) : ViewModel() { // MutableStateFlow demands an initial value, so the UI always has something to render. private val _uiState = MutableStateFlow(SearchUiState.Idle) val uiState: StateFlow = _uiState.asStateFlow() fun onQueryChanged(query: String) { _uiState.value = SearchUiState.Loading viewModelScope.launch { val results = repo.search(query) // update {} applies the change atomically, safe under concurrent callers. _uiState.update { SearchUiState.Success(results) } } } } ``` 흔한 후속 질문은 왜 가변 필드 대신 `asStateFlow()`를 노출하느냐입니다. 이는 UI에 읽기 전용 뷰를 넘겨 상태가 오직 ViewModel을 통해서만 바뀌도록 하여 단방향 데이터 흐름을 유지하게 합니다. 안드로이드의 [StateFlow와 SharedFlow 가이드](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow)도 정확히 이 패턴을 권장합니다. ## SharedFlow와 StateFlow: 이벤트에 맞는 타입 고르기 전형적인 함정 질문은 "StateFlow가 일회성 이벤트를 전달할 수 있는가?"입니다. 솔직한 답은 안전하게는 불가능하다는 것입니다. StateFlow는 conflate하고 중복을 제거하므로 빠른 갱신 아래에서 화면 전환 이벤트가 누락될 수 있고, 새 컬렉터가 보관된 값을 재생하기 때문에 구성 변경 시 다시 발화합니다. `replay = 0`인 `SharedFlow`가 두 문제를 모두 해결합니다. 아무것도 보관하지 않으며, 모든 발행은 발행 시점에 활성 상태인 컬렉터에게만 도달합니다. ```kotlin // CheckoutViewModel.kt class CheckoutViewModel : ViewModel() { // replay = 0: a late collector must NOT re-receive a past navigation event. private val _events = MutableSharedFlow(replay = 0) val events: SharedFlow = _events.asSharedFlow() fun onPaymentConfirmed() { viewModelScope.launch { // emit() suspends if the buffer is full; tryEmit() is the non-suspending variant. _events.emit(CheckoutEvent.NavigateToReceipt) } } } ``` 아키텍처 관련 질문에서는 상태와 이벤트를 분리하는 이면의 논리가 [MVVM vs MVI 비교](/blog/android/mvvm-vs-mvi-architecture)에 잘 드러나는데, MVI는 이벤트를 가변 상태가 아니라 명시적인 스트림으로 다룹니다. ## stateIn으로 cold Flow를 hot StateFlow로 변환하기 면접관은 리포지토리의 cold flow를 UI 상태로 바꾸는 방법을 자주 묻습니다. 답은 `stateIn`(하나의 값을 보관할 때) 또는 `shareIn`(현재 값 없이 방송할 때)입니다. `SharingStarted` 파라미터는 업스트림이 언제 활성화될지를 제어하며, `WhileSubscribed(5_000)`가 표준적인 선택입니다. 화면 회전 동안에는 flow를 살려 두면서도 화면이 닫힐 때는 누수 없이 정리해 주기 때문입니다. ```kotlin // ProfileViewModel.kt val profile: StateFlow = repo.profileStream() .stateIn( scope = viewModelScope, // Keep the upstream alive 5s after the last collector leaves, surviving rotation. started = SharingStarted.WhileSubscribed(5_000), initialValue = Profile.EMPTY ) ``` `stateIn`과 `shareIn`의 차이는 정확히 StateFlow와 SharedFlow의 차이와 같습니다. `stateIn`은 `initialValue`가 필요하고 상태를 보관하는 반면, `shareIn`은 `replay` 개수를 받아 방송합니다. [Kotlin 코루틴을 마스터하는 이 가이드](/blog/android/mastering-kotlin-coroutines)에서 더 넓은 코루틴 모델을 되짚어 보면 이 연산자들을 스코프 및 취소와 연결해 이해하는 데 도움이 됩니다. ## 안드로이드 생명주기에서 flow를 안전하게 수집하기 시니어급 질문은 화면이 백그라운드에 있을 때 작업을 누수시키지 않으면서 flow를 수집하는 방법입니다. 평범한 `launch` 안에서 수집하면 UI가 멈춘 뒤에도 계속 실행되어 CPU를 낭비하고 크래시 위험을 만듭니다. `repeatOnLifecycle(STARTED)`는 `STOP`에서 수집을 취소하고 `START`에서 다시 시작합니다. ```kotlin // ProfileFragment.kt (View system) viewLifecycleOwner.lifecycleScope.launch { // Collection restarts on STARTED and cancels on STOPPED: no wasted work in the background. viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.uiState.collect { state -> render(state) } } } ``` Jetpack Compose에서 이에 대응하는 것은 `collectAsStateWithLifecycle()`이며, 앱이 백그라운드로 가면 수집을 멈추고 돌아오면 재개합니다. ```kotlin // ProfileScreen.kt (Jetpack Compose) @Composable fun ProfileScreen(viewModel: ProfileViewModel) { // collectAsStateWithLifecycle stops collecting when the app is backgrounded. val state by viewModel.uiState.collectAsStateWithLifecycle() ProfileContent(state) } ``` 이러한 생명주기 인식 덕분에 `collectAsStateWithLifecycle`과 짝을 이룬 StateFlow가 현대 Compose 앱의 기본 상태 패턴이 되며, 이 점은 [Jetpack Compose 면접 질문](/blog/android/jetpack-compose-interview-questions)에서 더 자세히 다룹니다. ## StateFlow와 SharedFlow 발행을 테스트하는 방법 시니어 지원자를 가르는 지점이 바로 테스트입니다. 순진한 접근은 `stateFlow.value`를 한 번 읽는 것인데, 이는 중간 상태를 놓치고 SharedFlow는 아예 관찰할 수 없습니다. 표준적인 답은 Turbine 라이브러리를 언급하는 것입니다. Turbine은 각 발행이 도착할 때까지 대기하고, 기대한 값이 끝내 오지 않으면 테스트를 실패시킵니다. ```kotlin // SearchViewModelTest.kt @Test fun `emits Loading then Success on query`() = runTest { val viewModel = SearchViewModel(fakeRepo) viewModel.uiState.test { assertEquals(SearchUiState.Idle, awaitItem()) // initial value viewModel.onQueryChanged("kotlin") assertEquals(SearchUiState.Loading, awaitItem()) // intermediate state assertEquals(SearchUiState.Success(fakeResults), awaitItem()) cancelAndIgnoreRemainingEvents() } } ``` Turbine을 `runTest` 및 테스트 디스패처와 함께 쓰면 단언이 결정적으로 유지되며, `StandardTestDispatcher`와 `UnconfinedTestDispatcher`의 차이를 언급하면 실제 코루틴 테스트 경험이 있다는 신호가 됩니다. ## 자주 나오는 Kotlin Flow 후속 질문 면접관은 속사포 같은 확인 질문으로 마무리합니다. `SharingStarted.WhileSubscribed(5_000)`은 무엇을 하는가? 첫 구독자에서 업스트림을 시작하고, 마지막 구독이 해제된 뒤 5초가 지나면 중단합니다. 이 정도면 화면 회전을 버티기에 충분합니다. StateFlow는 왜 중복 값을 건너뛰는가? `equals()`로 비교하므로 같은 값을 발행하는 것은 no-op이며, 그래서 상태에 data class가 중요합니다. SharedFlow가 StateFlow처럼 동작할 수 있는가? `replay = 1`로 설정하면 마지막 값을 보관하지만, 여전히 동기적인 `.value`가 없고 중복을 제거하지도 않습니다. SharedFlow의 백프레셔는 무엇으로 제어하는가? `extraBufferCapacity`와 `onBufferOverflow` 파라미터이며, 이벤트에는 흔히 `BufferOverflow.DROP_OLDEST`를 선택합니다. 이 연산자들에 대한 더 깊은 연습은 [Kotlin 코루틴과 Flow 면접 모듈](/technologies/android/interview-questions/android-kotlin-coroutines-flow)에 있습니다. > **지원자를 탈락시키는 실수** > > 화면 전환이나 스낵바 이벤트에 `StateFlow`를 쓰는 것입니다. 마지막 값을 새 컬렉터에게 재생하므로 회전 이후 이벤트가 다시 발화하여 사용자가 두 번 이동하게 됩니다. `replay = 0`인 `SharedFlow`를 쓰거나, 처리 후 소거되는 일회성 이벤트로 모델링하세요. kotlinx.coroutines 소스와 [GitHub의 flow 패키지 레퍼런스](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core)는 면접 전에 훑어볼 만합니다. `StateFlow`와 `SharedFlow`의 KDoc이 conflation과 replay 보장을 정확하게 서술하고 있기 때문입니다. ## 결론 Kotlin Flow와 StateFlow, SharedFlow의 구분은 면접에서 정확한 표현을 쓰는 사람에게 보상을 줍니다. 챙겨 들어가야 할 핵심 정리는 다음과 같습니다. - `Flow`는 cold입니다. 프로듀서가 컬렉터마다 다시 실행되므로 온디맨드 데이터 파이프라인에 알맞습니다. - `StateFlow`는 hot이고 항상 값을 보관하며 conflate 및 중복 제거를 하므로 UI 상태를 위한 도구입니다. - `SharedFlow`는 hot이면서 초기값이 필수가 아니므로 일회성 이벤트에 올바른 선택입니다. - cold를 hot으로 바꿀 때는 `stateIn`이나 `shareIn`을 쓰고, 화면 회전을 버티려면 `SharingStarted.WhileSubscribed(5_000)`을 사용합니다. - 백그라운드 작업과 누수를 피하려면 `repeatOnLifecycle(STARTED)` 또는 `collectAsStateWithLifecycle()`로 수집합니다. - 이벤트는 절대 StateFlow로 전달하지 마세요. replay 동작 때문에 구성 변경 이후 이벤트가 다시 발화합니다. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ko/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview