Beginner: Kotlin · Coroutines and Flow · topic 16 of 50 · 25 min
Dispatchers and withContext
Explanation
Why dispatchers?
suspend lets a coroutine pause, but it does not choose a thread. Some work must be on the main thread (updating the UI), and some must stay away from it (reading a big file, compressing an image). A dispatcher is the part of the coroutine system that sends a coroutine to the right thread or pool of threads.
| Dispatcher | Runs on | Use for |
|---|---|---|
| Dispatchers.Main | The Android main (UI) thread | Updating UI state, calling other suspend functions |
| Dispatchers.IO | A large shared pool of threads | Blocking I/O: files, old blocking network or database APIs |
| Dispatchers.Default | A pool sized to the CPU cores | CPU-heavy work: sorting big lists, parsing large JSON, image processing |
viewModelScope and lifecycleScope start coroutines on Dispatchers.Main by default. That is fine, because suspend calls like Retrofit and Room switch to background threads internally.
A thread pool is a group of threads that are created once and reused for many small jobs, because creating a new thread for every job is slow. IO and Default share threads internally, but IO allows many more threads at once, since most of them are just waiting.
withContext: switch for a block
import kotlinx.coroutines.*
import java.io.File
suspend fun readReport(path: String): String = withContext(Dispatchers.IO) {
File(path).readText() // blocking file read, safely off the main thread
}
suspend fun sortedNames(names: List<String>): List<String> = withContext(Dispatchers.Default) {
names.sorted() // CPU work on the Default pool
}The main-safety rule
Google's guidance: a suspend function should be main-safe, meaning it is safe to call from the main thread. The function itself takes care of moving its blocking work to the right dispatcher with withContext. Then the callers, often ViewModels running on Main, never need to think about threads.
class ReportRepository {
suspend fun load(): String = withContext(Dispatchers.IO) {
// blocking work here
"report data"
}
}
// In a ViewModel (runs on Main):
// viewModelScope.launch {
// val text = repository.load() // main-safe: no thread juggling here
// _state.value = text
// }Testing tip: inject dispatchers
Hard-coding Dispatchers.IO inside classes makes tests harder. Many teams pass the dispatcher in through the constructor, with a default value, so tests can swap in a test dispatcher (module 10).
class ImageCompressor(private val cpu: CoroutineDispatcher = Dispatchers.Default) {
suspend fun compress(bytes: ByteArray): ByteArray = withContext(cpu) {
bytes.copyOf(bytes.size / 2) // pretend compression
}
}Common mistakes
- Wrapping Retrofit or Room suspend calls in withContext(Dispatchers.IO). Not harmful, but unnecessary: they are already main-safe.
- Doing heavy CPU work on Dispatchers.IO. IO has many threads meant for waiting; CPU work belongs on Default.
- Updating UI from a background dispatcher. Update state on Main (or through StateFlow, which is safe to collect on Main).
- Using GlobalScope.launch(Dispatchers.IO) to "run in the background". Use a structured scope plus withContext.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Dispatchers and withContext · 1/6
suspend lets a coroutine pause. A dispatcher decides which thread it runs on.
Prefer a video by a teacher? Find free videos on “kotlin coroutines dispatchers withContext IO Default Main android” on YouTube ↗
In real life: Exporting a PDF statement
A banking app lets users export six months of transactions as a PDF. Building the PDF is CPU work, and saving it is file I/O. The repository uses withContext(Dispatchers.Default) to build it and withContext(Dispatchers.IO) to write it. The ViewModel simply calls exportStatement() from viewModelScope and shows a progress indicator meanwhile; the UI never stutters.
import kotlinx.coroutines.*
suspend fun buildPdf(rows: Int): ByteArray = withContext(Dispatchers.Default) {
ByteArray(rows * 100) // pretend heavy layout work
}
suspend fun save(bytes: ByteArray): String = withContext(Dispatchers.IO) {
"statement.pdf (${bytes.size} bytes)" // pretend file write
}
fun main() = runBlocking {
val pdf = buildPdf(1200)
println("Saved " + save(pdf)) // Saved statement.pdf (120000 bytes)
}Practice (0/3 done)
Which dispatcher suits sorting a list of 2 million items?
What does withContext(Dispatchers.IO) { readFile() } return?
Write a main-safe suspend fun countWords(path: String): Int that reads a text file on Dispatchers.IO and counts words on Dispatchers.Default. Print Thread.currentThread().name inside each block to see the different threads, then call it from runBlocking.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Flow and StateFlowCoroutines and Flow · same module
- Unit tests with JUnit and coroutinesTesting Android apps
- Interview questions: KotlinAndroid career path
- Threads: doing things at the same timeJava Programming (Core to Spring Boot) · Multithreading and concurrency
- Virtual threads (Java 21)Java Programming (Core to Spring Boot) · Multithreading and concurrency
- ThreadsRust Programming · Concurrency and async
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which dispatcher runs on the Android UI thread?
Answer: Dispatchers.Main
Main is the UI thread.
What does "main-safe" mean for a suspend function?
Answer: It is safe to call from the main thread because it moves blocking work elsewhere itself
Callers do not need to switch threads.
On which dispatcher does viewModelScope.launch start by default?
Answer: Main
viewModelScope uses Dispatchers.Main (immediate).
Do you need withContext(Dispatchers.IO) around a Retrofit suspend call?
Answer: No, Retrofit suspend functions are already main-safe
The library handles threading.
Which annotation marks a JUnit test function?Related
What does suspend mean on a function?Related
Answer: It may pause without blocking the thread
suspend allows pausing; the dispatcher decides the thread.
What does launch return?Related
Answer: A Job
A Job lets you join or cancel the coroutine.
Which is the best description of a coroutine?Related
Answer: A lightweight task that can suspend without blocking a thread
Coroutines run on threads but are much lighter.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “Coroutines and Flow” before its exam: Flow and StateFlow.
- Module 4 exam: Coroutines and Flow · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Android Studio and your first app. 50 topics left in the course before the final exam.