# Kotlin Flow vs StateFlow vs SharedFlow:2026年のAndroid面接質問 > 2026年にAndroidの面接官が問う Kotlin Flow・StateFlow・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 年の Android 面接で最も頻出するテーマの一つです。この 3 つを混同すると、コルーチンに関する質問であっさり評価を落としかねません。StateFlow と SharedFlow はどちらも Flow の上に構築されたホットなストリームですが、解決する課題は異なります。前者は状態を保持するため、後者はイベントを配信するために存在します。以下では、Android の面接官が実際に投げかける質問を取り上げ、正確な回答と本番投入できるコードとともに解説します。 > **20秒で答えるポイント** > > `Flow` はコールドで、コレクターごとにプロデューサーを一度実行します。`StateFlow` はホットで conflate されるストリームであり、常に単一の現在値を保持するため UI 状態に最適です。`SharedFlow` は初期値を必須としないホットなストリームで、画面遷移やスナックバーのような一度きりのイベントに向いています。 ## Kotlin Flow、StateFlow、SharedFlow:核心となる違い StateFlow と SharedFlow は `SharedFlow` を特殊化したものであり、その `SharedFlow` 自体がホットな `Flow` です。面接官が確かめようとするのは、コールドとホットの違い、現在値を保持するかどうか、そして重複した値の発行時にそれぞれがどう振る舞うか、という点です。簡潔なメンタルモデルとしては、`Flow` はレシピ、`StateFlow` は単一の可変値、`SharedFlow` はイベントバス、と捉えるとよいでしょう。 | プロパティ | Flow(コールド) | StateFlow | SharedFlow | |---|---|---|---| | 温度 | コールド | ホット | ホット | | 初期値 | なし | 必須 | 任意(`replay` で指定) | | 現在値を保持 | いいえ | はい(`.value` 経由) | いいえ | | 重複を発行 | はい | いいえ(conflate + 重複排除) | 設定可能 | | コレクター追加時 | 毎回新しいストリーム | 共有 | 共有 | | 最適な用途 | 非同期データパイプライン | UI 状態 | 一度きりのイベント | [Kotlin コルーチンの公式ドキュメント](https://kotlinlang.org/docs/flow.html) は、このデフォルトでコールドという振る舞いを `Flow` を特徴づける性質として扱っています。強い回答はここから始まります。 ## なぜ Kotlin の Flow はデフォルトでコールドなのか コールドな flow は `collect()` が呼ばれるまで何もせず、コレクターごとにプロデューサーブロックを再実行します。同じコールドな flow に対して 2 つのコレクターがあれば、独立した 2 回のネットワーク呼び出しが発生します。これは最も出題される概念なので、短い例で回答を裏づけましょう。 ```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 } ``` プロデューサーがコレクターごとに再起動するため、コールドな flow はオンデマンドで実行すべきデータパイプラインのデフォルトとして適しています。問題になるのは、ある値を画面全体で共有する必要がある場合だけであり、まさにそこで StateFlow と SharedFlow の出番となります。 ## StateFlow の面接質問:常に値を発行する状態 `StateFlow` は常に値を持つホットな flow で、その最新値を新しいコレクターすべてにリプレイします。初期値が必須で、同期的な読み取りのために `.value` を公開し、発行を conflate します。つまり、高速に連続する更新では中間の値がスキップされることがあり、同じ値を 2 回設定しても等価性で重複排除されるため何も発行されません。これにより、`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 経由でしか変更できないようにし、単方向データフローを保ちます。Android 公式の [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 と MVI の比較](/blog/android/mvvm-vs-mvi-architecture) に現れます。そこでは MVI がイベントを可変状態ではなく明示的なストリームとして扱っています。 ## stateIn でコールドな Flow をホットな StateFlow へ変換する 面接官は、リポジトリのコールドな 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) で振り返ると、これらの演算子をスコープやキャンセルと結びつけて理解できます。 ## Android のライフサイクル上で 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 は各発行が届くまで suspend し、期待した値が来なければテストを失敗させます。 ```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()` で比較するため、等価な値を発行しても何も起きず、だからこそ状態には data class が重要になります。SharedFlow は StateFlow のように振る舞えるのでしょうか。`replay = 1` を設定すれば最後の値を保持しますが、それでも同期的な `.value` は持たず、重複排除も一切行いません。SharedFlow のバックプレッシャーは何が制御するのでしょうか。`extraBufferCapacity` と `onBufferOverflow` のパラメーターで、イベントには `BufferOverflow.DROP_OLDEST` がよく選ばれます。これらの演算子のより深い演習は [Kotlin コルーチンと Flow の面接モジュール](/technologies/android/interview-questions/android-kotlin-coroutines-flow) にあります。 > **候補者を不合格にする間違い** > > 画面遷移やスナックバーのイベントに `StateFlow` を使うことです。最後の値を新しいコレクターにリプレイするため、画面回転後にイベントが再発火し、ユーザーは 2 回遷移させられてしまいます。`replay = 0` の `SharedFlow` を使うか、処理後にクリアされる一度きりのイベントとしてモデル化しましょう。 kotlinx.coroutines のソースと、その [GitHub 上の flow パッケージリファレンス](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core) は面接前に目を通す価値があります。`StateFlow` と `SharedFlow` の KDoc が conflate とリプレイの保証を正確に述べているからです。 ## まとめ Kotlin Flow、StateFlow、SharedFlow の区別は、面接で正確な言葉づかいをすると評価されます。持ち込むべき要点は次のとおりです。 - `Flow` はコールドで、プロデューサーがコレクターごとに再実行されるため、オンデマンドのデータパイプラインに向いています。 - `StateFlow` はホットで常に値を保持し、conflate と重複排除を行うため、UI 状態のためのツールです。 - `SharedFlow` はホットで初期値を必須とせず、一度きりのイベントに適した選択肢です。 - `stateIn` または `shareIn` でコールドからホットへ変換し、画面回転を乗り切るために `SharingStarted.WhileSubscribed(5_000)` を使います。 - `repeatOnLifecycle(STARTED)` または `collectAsStateWithLifecycle()` で収集し、バックグラウンドでの処理とリークを避けます。 - イベントを StateFlow で配信してはいけません。そのリプレイの挙動により、構成変更後にイベントが再発火します。 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview