What it is
The coroutine runtime for Kotlin, providing structured concurrency, dispatchers, channels and Flow for asynchronous and reactive programming.
suspend functions can pause without blocking a thread. Scopes bound their children's lifetime, dispatchers choose the thread pool, and Flow models streams of values.
Installation
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")Getting started
The smallest useful thing you can do with it, and what each part means.
suspend fun loadDashboard(userId: String): Dashboard = coroutineScope {
// Both start now and run concurrently.
val profile = async { api.fetchProfile(userId) }
val orders = async { api.fetchOrders(userId) }
Dashboard(profile.await(), orders.await())
}
// coroutineScope does not return until both finish. If either throws,
// the other is cancelled and the exception propagates.
viewModelScope.launch {
val dashboard = withTimeout(5_000) { loadDashboard("u-1") }
render(dashboard)
}// CPU-bound work, sized to core count.
val result = withContext(Dispatchers.Default) { heavyComputation() }
// Blocking I/O — JDBC, file access, legacy libraries.
val rows = withContext(Dispatchers.IO) { jdbcTemplate.query(sql) }
// UI updates (Android).
withContext(Dispatchers.Main) { textView.text = result }Advanced usage
Where the library earns its place over a simpler alternative.
fun searchBooks(query: Flow<String>): Flow<List<Book>> = query
.debounce(300) // wait for typing to settle
.filter { it.length >= 2 }
.distinctUntilChanged()
.flatMapLatest { term -> // cancel the in-flight search
flow { emit(api.search(term)) }
.catch { emit(emptyList()) }
}
.flowOn(Dispatchers.IO)
// Hot state that survives collectors coming and going.
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Work continues after the screen closes
- It was launched in GlobalScope or an unbound scope. Use viewModelScope, lifecycleScope, or a scope you cancel.
- A coroutine silently stops working
- A catch (e: Exception) swallowed CancellationException. Catch specific exceptions, or rethrow cancellation explicitly.
Best practices
- Never use GlobalScope — work launched there outlives the component and leaks.
- Wrap blocking calls in withContext(Dispatchers.IO); do not block Default or Main.
- Never catch CancellationException with a blanket catch — rethrow it.
- Expose StateFlow rather than MutableStateFlow so callers cannot mutate your state.
Background
Why it exists, and what it was reacting to.
Coroutines are a language feature, but the scopes, dispatchers and Flow API that make them usable live in this library. Its defining idea is structured concurrency: a coroutine cannot outlive the scope that started it.
