Skip to Content

Câu hỏi phỏng vấn Coroutines & Flow

Phần này tổng hợp các câu hỏi về Kotlin CoroutinesFlow - nền tảng async programming trong Android.


1. Coroutines Basics

Q: Coroutine là gì? Khác gì Thread?

Trả lời:

Coroutine là lightweight concurrent unit có thể suspendresume mà không block thread.

AspectThreadCoroutine
WeightHeavy (~1MB stack)Lightweight (~few KB)
Context SwitchOS-level, expensiveUser-level, cheap
BlockingBlocks threadSuspends, frees thread
QuantityLimited (hundreds)Many (millions)
CreationExpensiveCheap
// Thread - blocks Thread { val result = networkCall() // Blocks this thread runOnUiThread { updateUI(result) } }.start() // Coroutine - suspends viewModelScope.launch { val result = networkCall() // Suspends, doesn't block updateUI(result) // Runs on Main after suspension } // Can launch millions of coroutines repeat(100_000) { launch { delay(1000) print(".") } }

📚 Tìm hiểu thêm: Coroutines trong Android


Q: suspend function là gì?

Trả lời:

suspend function là function có thể pauseresume execution mà không block thread.

// suspend function can only be called from coroutine or another suspend function suspend fun fetchUser(id: String): User { delay(1000) // Built-in suspend function return api.getUser(id) // Another suspend function } // ❌ Cannot call from regular function fun regularFunction() { fetchUser("123") // Compile error! } // ✅ Must call from coroutine viewModelScope.launch { val user = fetchUser("123") // OK } // ✅ Or from another suspend function suspend fun loadData() { val user = fetchUser("123") // OK }

Internal: Compiler transforms suspend functions to use Continuation (callbacks) internally.


Q: Dispatchers là gì? Có bao nhiêu loại?

Trả lời:

Dispatcher xác định thread nào coroutine chạy trên.

DispatcherThread PoolUse Case
Dispatchers.MainMain/UI threadUI updates
Dispatchers.IOShared pool (64+ threads)Network, disk I/O
Dispatchers.DefaultCPU coresCPU-intensive work
Dispatchers.UnconfinedCurrent threadTesting, special cases
viewModelScope.launch(Dispatchers.Main) { showLoading() val data = withContext(Dispatchers.IO) { api.fetchData() // Runs on IO thread } val processed = withContext(Dispatchers.Default) { heavyComputation(data) // Runs on Default } hideLoading() updateUI(processed) // Runs on Main }

💡 Mẹo: viewModelScope.launch mặc định dùng Dispatchers.Main.immediate


2. Coroutine Scope

Q: Các loại CoroutineScope trong Android?

Trả lời:

ScopeLifecycleUse Case
viewModelScopeViewModelUI-related async work
lifecycleScopeActivity/FragmentLifecycle-aware work
GlobalScopeApp process❌ Avoid! No lifecycle
CoroutineScope()CustomCustom lifecycle management
// ViewModel - auto-cancelled when ViewModel cleared class UserViewModel : ViewModel() { fun loadData() { viewModelScope.launch { val data = repository.getData() _state.value = data } } } // Activity/Fragment - auto-cancelled when lifecycle ends class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { // Cancelled when Activity destroyed repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.data.collect { updateUI(it) } } } } } // ❌ GlobalScope - never use! GlobalScope.launch { // Never cancelled, can cause memory leaks }

Q: Job và SupervisorJob khác nhau thế nào?

Trả lời:

TypeChild FailureParent Failure
JobCancels parent + siblingsCancels children
SupervisorJobDoesn’t affect siblingsCancels children
// Job - one failure cancels all val job = Job() val scope = CoroutineScope(job + Dispatchers.Default) scope.launch { task1() } scope.launch { throw Exception() } // Cancels task1 too! scope.launch { task3() } // Also cancelled // SupervisorJob - failures isolated val supervisorJob = SupervisorJob() val scope = CoroutineScope(supervisorJob + Dispatchers.Default) scope.launch { task1() } // Continues scope.launch { throw Exception() } // Only this fails scope.launch { task3() } // Continues // supervisorScope block viewModelScope.launch { supervisorScope { launch { task1() } // Independent launch { throw Exception() } // Fails alone launch { task3() } // Continues } }

Use SupervisorJob when:

  • Multiple independent tasks
  • One failure shouldn’t affect others
  • viewModelScope uses SupervisorJob by default

3. Structured Concurrency

Q: Structured Concurrency là gì?

Trả lời:

Structured Concurrency đảm bảo:

  1. Parent waits for all children to complete
  2. Cancellation propagates to children
  3. Errors propagate to parent
suspend fun loadDashboard(): Dashboard = coroutineScope { // Both run concurrently val user = async { api.getUser() } val posts = async { api.getPosts() } // Parent waits for both Dashboard(user.await(), posts.await()) } // This line only reached when both complete // Cancellation propagates val job = viewModelScope.launch { coroutineScope { launch { longTask1() } // Cancelled launch { longTask2() } // Cancelled } } job.cancel() // Cancels all children

Q: launch vs async?

Trả lời:

BuilderReturnUse Case
launchJobFire-and-forget
asyncDeferred<T>Need result
viewModelScope.launch { // launch - fire and forget launch { analytics.trackEvent() } // async - concurrent with result val user = async { api.getUser() } val posts = async { api.getPosts() } // Await results val data = CombinedData(user.await(), posts.await()) updateUI(data) } // Sequential vs Concurrent suspend fun sequential() { val a = api.callA() // Wait val b = api.callB() // Then this // Total: timeA + timeB } suspend fun concurrent() = coroutineScope { val a = async { api.callA() } // Start val b = async { api.callB() } // Start immediately a.await() to b.await() // Total: max(timeA, timeB) }

4. Error Handling

Q: Xử lý exceptions trong Coroutines?

Trả lời:

1. try-catch in suspend function:

viewModelScope.launch { try { val data = api.fetchData() _state.value = UiState.Success(data) } catch (e: Exception) { _state.value = UiState.Error(e.message) } }

2. CoroutineExceptionHandler:

val handler = CoroutineExceptionHandler { _, exception -> Log.e("Error", "Caught: ${exception.message}") _state.value = UiState.Error(exception.message) } viewModelScope.launch(handler) { api.fetchData() // Exception handled by handler }

3. supervisorScope for independent tasks:

supervisorScope { launch(handler) { riskyTask1() } // Fails independently launch(handler) { riskyTask2() } // Continues }

4. runCatching for Result:

suspend fun loadUser(id: String): Result<User> = runCatching { api.getUser(id) } // Usage viewModelScope.launch { loadUser("123") .onSuccess { _user.value = it } .onFailure { _error.value = it.message } }

Q: CancellationException là gì?

Trả lời:

CancellationException là special exception cho coroutine cancellation - không phải lỗi.

viewModelScope.launch { try { longRunningTask() } catch (e: CancellationException) { // ❌ Don't catch! Let it propagate throw e } catch (e: Exception) { // Handle other errors } } // ✅ Better pattern viewModelScope.launch { try { longRunningTask() } catch (e: Exception) { if (e is CancellationException) throw e handleError(e) } } // Or use runCatching carefully runCatching { suspendFunction() } .onSuccess { } .onFailure { if (it is CancellationException) throw it }

⚠️ Quan trọng: Luôn re-throw CancellationException để không chặn cancellation propagation!


5. Flow

Q: Flow là gì? Khác gì LiveData?

Trả lời:

Flow là cold asynchronous stream cho Kotlin, phù hợp cho reactive programming.

AspectLiveDataFlow
OriginsAndroidKotlin (multiplatform)
LifecycleBuilt-inManual with operators
OperatorsLimitedRich (map, filter, combine)
Cold/HotHotCold by default
TestingHarderEasier
// LiveData class ViewModel : ViewModel() { private val _data = MutableLiveData<List<Item>>() val data: LiveData<List<Item>> = _data } // Flow (Recommended) class ViewModel : ViewModel() { val data: StateFlow<List<Item>> = repository.getItems() .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) } // Collect in Compose @Composable fun ItemList(viewModel: ViewModel) { val items by viewModel.data.collectAsStateWithLifecycle() LazyColumn { items(items) { ItemRow(it) } } }

📚 Tìm hiểu thêm: Flow trong Android


Q: StateFlow vs SharedFlow?

Trả lời:

AspectStateFlowSharedFlow
Initial ValueRequiredOptional
ReplaysLatest value (1)Configurable
EqualitySkips duplicateDoesn’t skip
Use CaseUI stateEvents
class ViewModel : ViewModel() { // StateFlow - for UI state private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow<UiState> = _uiState.asStateFlow() // SharedFlow - for one-time events private val _events = MutableSharedFlow<Event>() val events: SharedFlow<Event> = _events.asSharedFlow() fun showMessage(text: String) { viewModelScope.launch { _events.emit(Event.ShowSnackbar(text)) } } } // Collect events LaunchedEffect(Unit) { viewModel.events.collect { event -> when (event) { is Event.ShowSnackbar -> snackbarHostState.showSnackbar(event.text) is Event.Navigate -> navController.navigate(event.route) } } }

Q: Flow operators thường dùng?

Trả lời:

// Transform repository.getUsers() .map { users -> users.filter { it.isActive } } .filter { it.isNotEmpty() } // Combine multiple flows combine(userFlow, settingsFlow) { user, settings -> ProfileState(user, settings) } // FlatMap searchQuery .debounce(300) // Wait 300ms after last change .distinctUntilChanged() // Skip if same .flatMapLatest { query -> // Cancel previous search repository.search(query) } // Error handling flow .catch { e -> emit(emptyList()) } // Handle upstream errors .onEach { result -> /* process */ } .launchIn(viewModelScope) // Retry repository.fetchData() .retry(3) { e -> e is IOException } .catch { emit(cachedData) }

6. Best Practices

Q: collectAsStateWithLifecycle vs collectAsState?

Trả lời:

FunctionLifecycle-awareUse Case
collectAsStateQuick testing
collectAsStateWithLifecycleProduction
// ❌ Continues collecting in background @Composable fun BadScreen(viewModel: ViewModel) { val state by viewModel.flow.collectAsState() } // ✅ Stops when lifecycle is less than STARTED @Composable fun GoodScreen(viewModel: ViewModel) { val state by viewModel.flow.collectAsStateWithLifecycle() }

Q: Testing Coroutines?

Trả lời:

@OptIn(ExperimentalCoroutinesApi::class) class ViewModelTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun `load data success`() = runTest { // Given val repository = FakeRepository() val viewModel = UserViewModel(repository) // When viewModel.loadData() // Then assertEquals(UiState.Success(data), viewModel.state.value) } } // Main dispatcher rule class MainDispatcherRule( private val dispatcher: TestDispatcher = UnconfinedTestDispatcher() ) : TestWatcher() { override fun starting(description: Description) { Dispatchers.setMain(dispatcher) } override fun finished(description: Description) { Dispatchers.resetMain() } }

📝 Quick Reference

// Scope viewModelScope.launch { } // ViewModel lifecycleScope.launch { } // Activity/Fragment // Dispatchers withContext(Dispatchers.IO) { } // I/O withContext(Dispatchers.Default) { } // CPU // Builders launch { } // Fire-and-forget async { }.await() // Get result // Flow flow.collect { } // Terminal operator flow.collectAsStateWithLifecycle() // In Compose flow.stateIn(scope, started, initial) // Convert to StateFlow // Error handling try { } catch (e: Exception) { if (e is CancellationException) throw e } runCatching { }.onSuccess { }.onFailure { }

📚 Tìm hiểu thêm: Coroutines, Flow

Last updated on