# Kotlin Flow проти StateFlow проти SharedFlow: питання на Android-співбесіді у 2026 > Питання про Kotlin Flow проти StateFlow проти SharedFlow, які Android-інтерв'юери ставлять у 2026 році, з чіткими відповідями, таблицею порівняння та готовим до продакшену кодом. - Published: 2026-07-01 - Updated: 2026-07-07 - Author: SharpSkill - Tags: android, kotlin, coroutines, flow, interview - Reading time: 9 min --- Kotlin Flow проти StateFlow проти SharedFlow — одна з найпоширеніших тем на Android-співбесідах у 2026 році, і плутанина між цими трьома типами швидко провалює раунд із корутинами. StateFlow і SharedFlow — це гарячі потоки, побудовані поверх Flow, але вони існують для різних задач: зберігання стану проти трансляції подій. Питання нижче — саме ті, які Android-інтерв'юери ставлять насправді, з точними відповідями та готовим до продакшену кодом. > **Відповідь за 20 секунд** > > `Flow` є холодним і запускає свій продюсер окремо для кожного колектора. `StateFlow` — це гарячий, зконфльований (conflated) потік, який завжди тримає одне поточне значення, ідеальний для стану UI. `SharedFlow` — це гарячий потік без обов'язкового початкового значення, ідеальний для одноразових подій на кшталт навігації чи снекбару. ## Kotlin Flow проти StateFlow проти SharedFlow: ключові відмінності StateFlow і SharedFlow є спеціалізаціями `SharedFlow`, який сам по собі є гарячим `Flow`. Інтерв'юери зазвичай перевіряють різницю між холодним і гарячим, чи зберігається поточне значення та як кожен тип поводиться з повторюваними емісіями. Стисла ментальна модель: `Flow` — це рецепт, `StateFlow` — одне мутабельне значення, а `SharedFlow` — шина подій. | Властивість | Flow (холодний) | StateFlow | SharedFlow | |---|---|---|---| | Температура | Холодний | Гарячий | Гарячий | | Початкове значення | Немає | Обов'язкове | Опційне (через `replay`) | | Тримає поточне значення | Ні | Так, через `.value` | Ні | | Емітить дублікати | Так | Ні (згортає + дедуплікує) | Налаштовується | | Додаткові колектори | Щоразу новий потік | Спільний | Спільний | | Найкраще підходить для | Асинхронних конвеєрів даних | Стану UI | Одноразових подій | [Офіційна документація Kotlin coroutines](https://kotlinlang.org/docs/flow.html) розглядає цю холодну за замовчуванням поведінку як визначальну властивість `Flow`, тож сильна відповідь починається саме звідти. ## Чому Kotlin Flow за замовчуванням холодний? Холодний потік нічого не робить, доки не викликано `collect()`, і повторно виконує свій блок-продюсер для кожного колектора. Два колектори на тому самому холодному потоці спричиняють два незалежні мережеві виклики. Це найчастіше перевірювана концепція, тож короткий приклад закріплює відповідь. ```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 } ``` Оскільки продюсер перезапускається для кожного колектора, холодні потоки — правильний вибір за замовчуванням для конвеєрів даних, які мають виконуватися на вимогу. Вони стають проблемою лише тоді, коли значення потрібно розділити між усім екраном, і саме тут у гру вступають StateFlow і SharedFlow. ## Питання про StateFlow: стан, який завжди емітить `StateFlow` — це гарячий потік, що завжди має значення й відтворює це останнє значення для кожного нового колектора. Він вимагає початкового значення, надає `.value` для синхронного читання і згортає емісії: швидкі послідовні оновлення можуть пропускати проміжні значення, а встановлення того самого значення двічі не емітить нічого, бо він дедуплікує за рівністю. Це робить його сучасною заміною `LiveData` у `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) } } } } ``` Поширене продовження: навіщо надавати `asStateFlow()` замість мутабельного поля? Воно передає UI лише для читання, тож стан може змінюватися тільки через ViewModel, що зберігає однонаправлений потік даних недоторканим. Власний [гайд Android про StateFlow і SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow) рекомендує саме цей патерн. ## SharedFlow проти StateFlow: вибір правильного типу для подій Класичне питання-пастка: «чи може StateFlow доставляти одноразові події?» Чесна відповідь — ні, принаймні не безпечно. Оскільки StateFlow згортає й дедуплікує, подія навігації може загубитися під час швидких оновлень, а ще вона повторно спрацьовує під час зміни конфігурації, бо новий колектор відтворює збережене значення. `SharedFlow` із `replay = 0` виправляє обидві проблеми: нічого не зберігається, і кожна емісія досягає лише тих колекторів, що активні на момент емісії. ```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 трактує події як явний потік, а не як мутабельний стан. ## Перетворення холодного Flow на гарячий StateFlow за допомогою stateIn Інтерв'юери часто питають, як перетворити холодний потік репозиторію на стан UI. Відповідь — `stateIn` (для одного збереженого значення) або `shareIn` (для трансляції без поточного значення). Параметр `SharingStarted` контролює, коли upstream активний, а `WhileSubscribed(5_000)` є стандартним вибором, бо він тримає потік живим під час повороту екрана, але не витікає, коли екран закривається. ```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 coroutines](/blog/android/mastering-kotlin-coroutines) допомагає пов'язати ці оператори зі скоупами й скасуванням. ## Безпечне збирання потоків у життєвому циклі Android Питання рівня senior — як збирати потік без витоку роботи, поки екран у фоні. Збирання всередині звичайного `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) } ``` Саме ця обізнаність із життєвим циклом робить StateFlow у парі з `collectAsStateWithLifecycle` патерном стану за замовчуванням у сучасних Compose-застосунках — момент, який детальніше розкрито в цих [питаннях на співбесіді з Jetpack Compose](/blog/android/jetpack-compose-interview-questions). ## Як тестувати емісії StateFlow і SharedFlow Тестування — саме те, чим senior-кандидати вирізняються. Наївний підхід читає `stateFlow.value` один раз, але це пропускає проміжні стани й узагалі не може спостерігати за SharedFlow. Стандартна відповідь називає бібліотеку 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)`? Він запускає upstream на першому підписнику й зупиняє його через 5 секунд після відписки останнього — цього достатньо, щоб пережити поворот екрана. Чому StateFlow пропускає повторювані значення? Він порівнює через `equals()`, тож емісія рівного значення нічого не робить, і саме тому data-класи важливі для стану. Чи може SharedFlow поводитися як StateFlow? Встановлення `replay = 1` змушує його зберігати останнє значення, але йому все одно бракує синхронного `.value` і він ніколи не дедуплікує. Що контролює back-pressure у SharedFlow? Параметри `extraBufferCapacity` та `onBufferOverflow`, де `BufferOverflow.DROP_OLDEST` є поширеним вибором для подій. Глибша практика з цими операторами є в [модулі співбесід з Kotlin coroutines і Flow](/technologies/android/interview-questions/android-kotlin-coroutines-flow). > **Помилка, яка провалює кандидатів** > > Використання `StateFlow` для подій навігації чи снекбару. Оскільки він відтворює своє останнє значення для нових колекторів, подія повторно спрацьовує після повороту екрана й користувача перенавігує двічі. Слід брати `SharedFlow` із `replay = 0` або моделювати одноразову подію, яка очищається після обробки. Вихідний код kotlinx.coroutines і його [довідник пакета flow на GitHub](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core) варто переглянути перед співбесідою, оскільки KDoc для `StateFlow` і `SharedFlow` точно описує гарантії згортання й відтворення. ## Висновок Розрізнення Kotlin Flow проти StateFlow проти SharedFlow винагороджує точність формулювань на співбесіді. Ключові висновки, які варто взяти з собою: - `Flow` холодний: продюсер перезапускається для кожного колектора, тож він підходить для конвеєрів даних на вимогу. - `StateFlow` гарячий, завжди тримає значення, згортає й дедуплікує, що робить його інструментом для стану 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/uk/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview