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.

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.
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 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.
fun searchResults(query: String): Flow<List<Result>> = 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.
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>(SearchUiState.Idle)
val uiState: StateFlow<SearchUiState> = _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 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.
class CheckoutViewModel : ViewModel() {
// replay = 0: a late collector must NOT re-receive a past navigation event.
private val _events = MutableSharedFlow<CheckoutEvent>(replay = 0)
val events: SharedFlow<CheckoutEvent> = _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, 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.
val profile: StateFlow<Profile> = 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 helps connect these operators to scopes and cancellation.
Ready to ace your Android interviews?
Practice with our interactive simulators, flashcards, and technical tests.
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.
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.
@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.
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.
@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.
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 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:
Flowis cold: the producer re-runs for every collector, so it fits on-demand data pipelines.StateFlowis hot, always holds a value, and conflates and deduplicates, making it the tool for UI state.SharedFlowis hot with no required initial value, making it the correct choice for one-time events.- Convert cold to hot with
stateInorshareIn, and useSharingStarted.WhileSubscribed(5_000)to survive rotation. - Collect with
repeatOnLifecycle(STARTED)orcollectAsStateWithLifecycle()to avoid background work and leaks. - Never deliver events through StateFlow: its replay behavior re-fires them after a configuration change.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Tags
Share
Related articles

Kotlin 2.3 for Android: Name-Based Destructuring, KMP and Interview Questions 2026
Kotlin 2.3 interview questions covering name-based destructuring, Kotlin Multiplatform, context parameters, coroutines and Flow. Prepare for Android developer interviews in 2026 with real-world code examples.

Android 16 in 2026: New APIs, Desktop Mode and Interview Questions
Deep dive into Android 16 (API 36) covering desktop mode, ProgressStyle notifications, predictive back, edge-to-edge enforcement and the interview questions that come with them.

Top 20 Jetpack Compose Interview Questions in 2026
The 20 most-asked Jetpack Compose interview questions: recomposition, state management, navigation, performance, and architecture patterns.