# Kotlin Flow vs StateFlow vs SharedFlow: Android Interview Questions in 2026 > The Kotlin Flow vs StateFlow vs SharedFlow questions Android interviewers ask in 2026, with clear answers, a comparison table, and production-ready code. - Published: 2026-07-01 - Updated: 2026-07-06 - Author: SharpSkill - Tags: android, kotlin, coroutines, flow, interview - Reading time: 9 min --- Kotlin Flow vs StateFlow vs SharedFlow is one of the most common Android interview topics in 2026, and mixing up the three is a quick way to lose a coroutines round. StateFlow and SharedFlow are both hot streams built on top of Flow, but they exist to solve different problems: holding state versus broadcasting events. The questions below are the ones Android interviewers actually ask, with precise answers and production-ready code. > **The 20-second answer** > > A `Flow` is cold and runs its producer once per collector. `StateFlow` is a hot, conflated stream that always holds a single current value, ideal for UI state. `SharedFlow` is a hot stream with no required initial value, ideal for one-time events like navigation or a snackbar. ## Kotlin Flow vs StateFlow vs SharedFlow: the core differences StateFlow and SharedFlow are specializations of `SharedFlow`, which itself is a hot `Flow`. The distinction interviewers probe is cold versus hot, whether a current value is retained, and how each behaves with duplicate emissions. A concise mental model: `Flow` is a recipe, `StateFlow` is a single mutable value, and `SharedFlow` is an event bus. | Property | Flow (cold) | StateFlow | SharedFlow | |---|---|---|---| | Temperature | Cold | Hot | Hot | | Initial value | None | Required | Optional (via `replay`) | | Holds current value | No | Yes, via `.value` | No | | Emits duplicates | Yes | No (conflated + deduped) | Configurable | | Extra collectors | New stream each time | Shared | Shared | | Best for | Async data pipelines | UI state | One-time events | The [official Kotlin coroutines documentation](https://kotlinlang.org/docs/flow.html) treats this cold-by-default behavior as the defining property of `Flow`, so a strong answer starts there. ## Why is a Kotlin Flow cold by default? A cold flow does nothing until `collect()` is called, and it re-executes its producer block for every collector. Two collectors on the same cold flow trigger two independent network calls. This is the single most tested concept, so a short example anchors the answer. ```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 } ``` Because the producer restarts per collector, cold flows are the right default for data pipelines that should run on demand. They become a problem only when a value must be shared across an entire screen, which is exactly where StateFlow and SharedFlow come in. ## StateFlow interview questions: state that always emits `StateFlow` is a hot flow that always has a value and replays that latest value to every new collector. It requires an initial value, exposes `.value` for synchronous reads, and conflates emissions: fast successive updates can skip intermediate values, and setting the same value twice emits nothing because it deduplicates via equality. This makes it the modern replacement for `LiveData` in a `ViewModel`. ```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) } } } } ``` A common follow-up: why expose `asStateFlow()` instead of the mutable field? It hands the UI a read-only view so the state can only change through the ViewModel, which keeps the unidirectional data flow intact. Android's own [StateFlow and SharedFlow guide](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow) recommends exactly this pattern. ## SharedFlow vs StateFlow: choosing the right type for events The classic trap question is "can StateFlow deliver one-time events?" The honest answer is no, not safely. Because StateFlow conflates and deduplicates, a navigation event can be dropped under rapid updates, and it re-fires on configuration change because a new collector replays the retained value. `SharedFlow` with `replay = 0` fixes both: nothing is retained, and every emission reaches only the collectors active at emit time. ```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) } } } ``` For questions on architecture, the deeper reasoning behind separating state from events shows up in the [MVVM vs MVI comparison](/blog/android/mvvm-vs-mvi-architecture), where MVI treats events as an explicit stream rather than mutable state. ## Converting a cold Flow to a hot StateFlow with stateIn Interviewers frequently ask how to turn a repository's cold flow into UI state. The answer is `stateIn` (for a single retained value) or `shareIn` (for a broadcast without a current value). The `SharingStarted` parameter controls when the upstream is active, and `WhileSubscribed(5_000)` is the standard choice because it keeps the flow alive across a rotation without leaking it when the screen closes. ```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 ) ``` The difference between `stateIn` and `shareIn` is precisely the difference between StateFlow and SharedFlow: `stateIn` needs an `initialValue` and holds state, while `shareIn` takes a `replay` count and broadcasts. Reviewing the broader coroutines model in [this guide to mastering Kotlin coroutines](/blog/android/mastering-kotlin-coroutines) helps connect these operators to scopes and cancellation. ## Collecting flows safely on the Android lifecycle A senior-level question is how to collect a flow without leaking work while the screen is in the background. Collecting inside a plain `launch` keeps running when the UI is stopped, wasting CPU and risking crashes. `repeatOnLifecycle(STARTED)` cancels collection on `STOP` and restarts it on `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) } } } ``` In Jetpack Compose the equivalent is `collectAsStateWithLifecycle()`, which stops collecting when the app is backgrounded and resumes on return. ```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) } ``` This lifecycle awareness is why StateFlow paired with `collectAsStateWithLifecycle` is the default state pattern in modern Compose apps, a point covered further in these [Jetpack Compose interview questions](/blog/android/jetpack-compose-interview-questions). ## How to test StateFlow and SharedFlow emissions Testing is where senior candidates separate themselves. The naive approach reads `stateFlow.value` once, but that misses intermediate states and cannot observe a SharedFlow at all. The standard answer names the Turbine library, which suspends until each emission arrives and fails the test if an expected value never comes. ```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() } } ``` Pairing Turbine with `runTest` and a test dispatcher keeps the assertions deterministic, and mentioning `StandardTestDispatcher` versus `UnconfinedTestDispatcher` signals real coroutine testing experience. ## Common Kotlin Flow interview follow-ups Interviewers close with rapid-fire checks. What does `SharingStarted.WhileSubscribed(5_000)` do? It starts the upstream on the first subscriber and stops it 5 seconds after the last unsubscribes, which is long enough to survive a rotation. Why does StateFlow skip duplicate values? It compares with `equals()`, so emitting an equal value is a no-op, which is why data classes matter for state. Can SharedFlow behave like StateFlow? Setting `replay = 1` makes it retain the last value, but it still lacks a synchronous `.value` and never deduplicates. What controls back-pressure on a SharedFlow? The `extraBufferCapacity` and `onBufferOverflow` parameters, with `BufferOverflow.DROP_OLDEST` a common choice for events. Deeper practice on these operators lives in the [Kotlin coroutines and Flow interview module](/technologies/android/interview-questions/android-kotlin-coroutines-flow). > **The mistake that fails candidates** > > Using `StateFlow` for navigation or snackbar events. Because it replays its last value to new collectors, the event re-fires after a rotation and the user is navigated twice. Reach for `SharedFlow` with `replay = 0`, or model a one-shot event that is cleared after handling. The kotlinx.coroutines source and its [flow package reference on GitHub](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core) are worth skimming before an interview, since the KDoc on `StateFlow` and `SharedFlow` states the conflation and replay guarantees precisely. ## Conclusion The Kotlin Flow vs StateFlow vs SharedFlow distinction rewards precise language in an interview. Key takeaways to carry in: - `Flow` is cold: the producer re-runs for every collector, so it fits on-demand data pipelines. - `StateFlow` is hot, always holds a value, and conflates and deduplicates, making it the tool for UI state. - `SharedFlow` is hot with no required initial value, making it the correct choice for one-time events. - Convert cold to hot with `stateIn` or `shareIn`, and use `SharingStarted.WhileSubscribed(5_000)` to survive rotation. - Collect with `repeatOnLifecycle(STARTED)` or `collectAsStateWithLifecycle()` to avoid background work and leaks. - Never deliver events through StateFlow: its replay behavior re-fires them after a configuration change. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview