# Kotlin Flow vs StateFlow vs SharedFlow: คำถามสัมภาษณ์ Android ปี 2026 > คำถาม Kotlin Flow vs StateFlow vs 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 vs StateFlow vs SharedFlow เป็นหนึ่งในหัวข้อสัมภาษณ์ Android ที่พบบ่อยที่สุดในปี 2026 และการสับสนระหว่างทั้งสามตัวนี้คือทางลัดสู่การตกรอบ coroutines StateFlow และ SharedFlow ต่างก็เป็น hot stream ที่สร้างขึ้นบน Flow แต่มีไว้เพื่อแก้ปัญหาคนละอย่าง คือการเก็บสถานะ (state) กับการกระจายอีเวนต์ (event) คำถามด้านล่างนี้คือคำถามที่ผู้สัมภาษณ์ Android ถามจริง พร้อมคำตอบที่แม่นยำและโค้ดที่พร้อมใช้งานจริง > **คำตอบภายใน 20 วินาที** > > `Flow` เป็นแบบ cold และรัน producer หนึ่งครั้งต่อ collector หนึ่งตัว `StateFlow` เป็น hot stream แบบ conflated ที่เก็บค่าปัจจุบันเพียงค่าเดียวไว้เสมอ เหมาะกับสถานะของ UI ส่วน `SharedFlow` เป็น hot stream ที่ไม่จำเป็นต้องมีค่าเริ่มต้น เหมาะกับอีเวนต์ที่เกิดขึ้นครั้งเดียว เช่น การนำทาง (navigation) หรือ snackbar ## Kotlin Flow vs StateFlow vs SharedFlow: ความแตกต่างหลัก StateFlow และ SharedFlow เป็นรูปแบบเฉพาะทาง (specialization) ของ `SharedFlow` ซึ่งตัวมันเองก็เป็น hot `Flow` สิ่งที่ผู้สัมภาษณ์มักเจาะลึกคือความต่างระหว่าง cold กับ hot ว่ามีการเก็บค่าปัจจุบันไว้หรือไม่ และแต่ละตัวจัดการกับการ emit ค่าซ้ำอย่างไร ภาพจำง่ายๆ คือ `Flow` เปรียบเหมือนสูตรอาหาร `StateFlow` เปรียบเหมือนค่าที่เปลี่ยนแปลงได้เพียงค่าเดียว และ `SharedFlow` เปรียบเหมือน event bus | คุณสมบัติ | Flow (cold) | StateFlow | SharedFlow | |---|---|---|---| | อุณหภูมิ | Cold | Hot | Hot | | ค่าเริ่มต้น | ไม่มี | จำเป็นต้องมี | มีหรือไม่ก็ได้ (ผ่าน `replay`) | | เก็บค่าปัจจุบัน | ไม่ | ใช่ ผ่าน `.value` | ไม่ | | emit ค่าซ้ำ | ได้ | ไม่ (conflated + ตัดค่าซ้ำ) | ตั้งค่าได้ | | collector เพิ่มเติม | สร้าง stream ใหม่ทุกครั้ง | ใช้ร่วมกัน | ใช้ร่วมกัน | | เหมาะสำหรับ | ไปป์ไลน์ข้อมูลแบบ async | สถานะ UI | อีเวนต์ที่เกิดครั้งเดียว | [เอกสารทางการของ Kotlin coroutines](https://kotlinlang.org/docs/flow.html) ถือว่าพฤติกรรม cold-by-default นี้คือคุณสมบัติหลักที่นิยาม `Flow` ดังนั้นคำตอบที่ดีจึงควรเริ่มจากจุดนี้ ## ทำไม Kotlin Flow ถึงเป็น cold โดยค่าเริ่มต้น? cold flow จะไม่ทำงานใดๆ จนกว่าจะมีการเรียก `collect()` และมันจะรันบล็อก producer ใหม่ทุกครั้งสำหรับ collector แต่ละตัว collector สองตัวบน cold flow เดียวกันจะทำให้เกิดการเรียก network สองครั้งที่แยกจากกัน นี่คือแนวคิดที่ถูกทดสอบบ่อยที่สุด ตัวอย่างสั้นๆ จึงช่วยยึดคำตอบให้ชัดเจน ```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 } ``` เนื่องจาก producer เริ่มทำงานใหม่สำหรับแต่ละ collector cold flow จึงเป็นตัวเลือกเริ่มต้นที่เหมาะสมสำหรับไปป์ไลน์ข้อมูลที่ควรทำงานตามความต้องการ (on demand) มันจะกลายเป็นปัญหาก็ต่อเมื่อต้องแชร์ค่าเดียวกันไปทั้งหน้าจอ ซึ่งเป็นจุดที่ StateFlow และ SharedFlow เข้ามามีบทบาทพอดี ## คำถามสัมภาษณ์เรื่อง StateFlow: สถานะที่ emit เสมอ `StateFlow` เป็น hot flow ที่มีค่าอยู่เสมอ และจะ replay ค่าล่าสุดนั้นให้กับ collector ใหม่ทุกตัว มันจำเป็นต้องมีค่าเริ่มต้น เปิดให้อ่านค่าแบบ synchronous ผ่าน `.value` และทำ conflate การ emit กล่าวคือการอัปเดตต่อเนื่องอย่างรวดเร็วอาจข้ามค่าระหว่างกลางไป และการกำหนดค่าเดิมซ้ำจะไม่ emit อะไรเลยเพราะมันตัดค่าซ้ำด้วยการเปรียบเทียบความเท่ากัน (equality) คุณสมบัตินี้ทำให้มันเป็นตัวแทนสมัยใหม่ของ `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()` แทนที่จะใช้ฟิลด์แบบ mutable โดยตรง? เพราะมันมอบมุมมองแบบอ่านอย่างเดียว (read-only) ให้กับ UI ทำให้สถานะเปลี่ยนแปลงได้ผ่าน ViewModel เท่านั้น ซึ่งช่วยรักษาการไหลของข้อมูลแบบทิศทางเดียว (unidirectional data flow) เอาไว้ [คู่มือ StateFlow และ SharedFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow) ของ Android เองก็แนะนำรูปแบบนี้อย่างชัดเจน ## SharedFlow vs StateFlow: เลือกชนิดที่เหมาะกับอีเวนต์ คำถามหลุมพรางคลาสสิกคือ "StateFlow ส่งอีเวนต์ที่เกิดครั้งเดียวได้ไหม?" คำตอบตามตรงคือไม่ได้ อย่างน้อยก็ไม่ปลอดภัย เพราะ StateFlow ทำ conflate และตัดค่าซ้ำ อีเวนต์การนำทางจึงอาจถูกทิ้งไปเมื่อมีการอัปเดตอย่างรวดเร็ว และมันจะยิงซ้ำเมื่อเกิดการเปลี่ยน configuration เพราะ collector ใหม่จะ replay ค่าที่เก็บไว้ `SharedFlow` ที่ตั้ง `replay = 0` แก้ปัญหาทั้งสองข้อได้ คือไม่มีการเก็บค่าใดไว้ และทุกการ emit จะไปถึงเฉพาะ collector ที่ทำงานอยู่ ณ ตอนที่ emit เท่านั้น ```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 มองว่าอีเวนต์เป็น stream ที่ชัดเจน แทนที่จะเป็นสถานะแบบ mutable ## การแปลง cold Flow เป็น hot StateFlow ด้วย stateIn ผู้สัมภาษณ์มักถามวิธีเปลี่ยน cold flow จาก repository ให้กลายเป็นสถานะของ UI คำตอบคือ `stateIn` (สำหรับค่าเดียวที่ถูกเก็บไว้) หรือ `shareIn` (สำหรับการกระจายที่ไม่มีค่าปัจจุบัน) พารามิเตอร์ `SharingStarted` ควบคุมว่าเมื่อใดที่ upstream จะทำงาน และ `WhileSubscribed(5_000)` เป็นตัวเลือกมาตรฐาน เพราะมันทำให้ flow ยังทำงานอยู่ตลอดการหมุนจอ โดยไม่ทำให้เกิด leak เมื่อปิดหน้าจอ ```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` และทำหน้าที่กระจาย การทบทวนโมเดล coroutines ในภาพกว้างใน [คู่มือการเชี่ยวชาญ Kotlin coroutines นี้](/blog/android/mastering-kotlin-coroutines) ช่วยเชื่อมโยงตัวดำเนินการเหล่านี้เข้ากับ scope และการยกเลิก (cancellation) ## การ collect flow อย่างปลอดภัยตาม lifecycle ของ Android คำถามระดับ senior คือจะ collect flow อย่างไรโดยไม่ทำให้เกิดงานที่รั่วไหลขณะที่หน้าจออยู่เบื้องหลัง (background) การ collect ภายใน `launch` แบบธรรมดาจะยังทำงานต่อไปแม้ UI จะหยุดแล้ว ทำให้สิ้นเปลือง CPU และเสี่ยงต่อการ crash `repeatOnLifecycle(STARTED)` จะยกเลิกการ collect เมื่อถึง `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()` ซึ่งจะหยุด collect เมื่อแอปถูกย้ายไปเบื้องหลัง และกลับมาทำงานต่อเมื่อกลับเข้าแอป ```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) } ``` ความตระหนักในเรื่อง lifecycle นี้เองคือเหตุผลที่ StateFlow ควบคู่กับ `collectAsStateWithLifecycle` กลายเป็นรูปแบบการจัดการสถานะเริ่มต้นในแอป Compose สมัยใหม่ ประเด็นนี้มีอธิบายเพิ่มเติมใน [คำถามสัมภาษณ์ Jetpack Compose](/blog/android/jetpack-compose-interview-questions) ## วิธีทดสอบการ emit ของ StateFlow และ SharedFlow การทดสอบคือจุดที่แยกผู้สมัครระดับ senior ออกจากคนอื่น วิธีที่มือใหม่มักใช้คืออ่าน `stateFlow.value` เพียงครั้งเดียว แต่วิธีนี้พลาดสถานะระหว่างกลาง และไม่สามารถสังเกต SharedFlow ได้เลย คำตอบมาตรฐานคือการเอ่ยถึงไลบรารี Turbine ซึ่งจะ suspend จนกว่าการ emit แต่ละครั้งจะมาถึง และทำให้เทสต์ล้มเหลวหากค่าที่คาดหวังไม่เคยมาถึง ```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` และ test dispatcher ช่วยให้การ assert มีผลลัพธ์ที่แน่นอน (deterministic) และการพูดถึงความต่างระหว่าง `StandardTestDispatcher` กับ `UnconfinedTestDispatcher` แสดงถึงประสบการณ์การทดสอบ coroutine อย่างแท้จริง ## คำถามต่อเนื่องเรื่อง Kotlin Flow ที่พบบ่อย ผู้สัมภาษณ์มักปิดท้ายด้วยคำถามยิงเร็ว `SharingStarted.WhileSubscribed(5_000)` ทำอะไร? มันเริ่ม upstream เมื่อมีผู้ subscribe รายแรก และหยุดหลังจากผู้ subscribe รายสุดท้ายยกเลิกไป 5 วินาที ซึ่งนานพอที่จะรอดจากการหมุนจอ ทำไม StateFlow ถึงข้ามค่าที่ซ้ำกัน? เพราะมันเปรียบเทียบด้วย `equals()` การ emit ค่าที่เท่ากันจึงไม่มีผลอะไร นี่คือเหตุผลว่าทำไม data class จึงสำคัญต่อสถานะ SharedFlow ทำตัวเหมือน StateFlow ได้ไหม? การตั้ง `replay = 1` ทำให้มันเก็บค่าล่าสุดไว้ แต่มันก็ยังไม่มี `.value` แบบ synchronous และไม่เคยตัดค่าซ้ำ อะไรควบคุม back-pressure บน SharedFlow? พารามิเตอร์ `extraBufferCapacity` และ `onBufferOverflow` โดยที่ `BufferOverflow.DROP_OLDEST` เป็นตัวเลือกที่นิยมสำหรับอีเวนต์ การฝึกฝนตัวดำเนินการเหล่านี้เชิงลึกอยู่ใน [โมดูลสัมภาษณ์ Kotlin coroutines และ Flow](/technologies/android/interview-questions/android-kotlin-coroutines-flow) > **ข้อผิดพลาดที่ทำให้ผู้สมัครตกรอบ** > > การใช้ `StateFlow` สำหรับอีเวนต์การนำทางหรือ snackbar เพราะมัน replay ค่าล่าสุดให้ collector ใหม่ อีเวนต์จึงยิงซ้ำหลังการหมุนจอ และผู้ใช้จะถูกนำทางซ้ำสองครั้ง ควรเลือกใช้ `SharedFlow` ที่ตั้ง `replay = 0` หรือออกแบบอีเวนต์แบบครั้งเดียว (one-shot) ที่ถูกล้างหลังจากจัดการเสร็จ ซอร์สโค้ดของ kotlinx.coroutines และ [เอกสารอ้างอิงแพ็กเกจ flow บน GitHub](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core) คุ้มค่าที่จะไล่อ่านก่อนการสัมภาษณ์ เพราะ KDoc ของ `StateFlow` และ `SharedFlow` ระบุการรับประกันเรื่อง conflation และ replay ไว้อย่างแม่นยำ ## บทสรุป ความต่างระหว่าง Kotlin Flow vs StateFlow vs SharedFlow ตอบแทนการใช้ภาษาที่แม่นยำในการสัมภาษณ์ ประเด็นสำคัญที่ควรจดจำ: - `Flow` เป็น cold: producer จะรันใหม่สำหรับทุก collector จึงเหมาะกับไปป์ไลน์ข้อมูลแบบตามความต้องการ - `StateFlow` เป็น hot เก็บค่าไว้เสมอ อีกทั้งทำ conflate และตัดค่าซ้ำ จึงเป็นเครื่องมือสำหรับสถานะ UI - `SharedFlow` เป็น hot ที่ไม่จำเป็นต้องมีค่าเริ่มต้น จึงเป็นตัวเลือกที่ถูกต้องสำหรับอีเวนต์ที่เกิดครั้งเดียว - แปลง cold เป็น hot ด้วย `stateIn` หรือ `shareIn` และใช้ `SharingStarted.WhileSubscribed(5_000)` เพื่อให้รอดจากการหมุนจอ - collect ด้วย `repeatOnLifecycle(STARTED)` หรือ `collectAsStateWithLifecycle()` เพื่อหลีกเลี่ยงงานเบื้องหลังและการ leak - อย่าส่งอีเวนต์ผ่าน StateFlow เด็ดขาด เพราะพฤติกรรม replay ของมันจะยิงซ้ำหลังการเปลี่ยน configuration --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/th/blog/android/kotlin-flow-vs-stateflow-vs-sharedflow-interview