Coroutines without the jargon
Lesson 3 of 51 min read
Suspension, dispatchers, scopes and cancellation, in the order they get asked.
A coroutine is work that can pause and resume without holding a thread while it waits. That one sentence answers the first question; the rest of this lesson is the follow-ups.
Why not just threads
A thread costs about a megabyte of stack and an OS context switch. A coroutine is an object. When a coroutine suspends, the thread is handed back and picks up other work, which is why 100,000 coroutines are fine and 100,000 threads are not.
Dispatchers: three, and one rule
- Main — UI and state.
- IO — waiting on network, disk, database.
- Default — CPU work: parsing, sorting, image maths.
The rule: a suspend function must be safe to call from anywhere. Put withContext(Dispatchers.IO) inside the repository, not in the ViewModel.
suspend fun user(id: String): User = withContext(Dispatchers.IO) { api.user(id) }
Scopes and structured concurrency
Every coroutine has a parent, and cancelling the parent cancels the children. That is why viewModelScope matters: when the ViewModel is cleared, the in-flight work is cancelled for you.
GlobalScope.launch has no parent, so nothing cancels it. If you say those two words in an interview, say them as the thing you avoid.
launch vs async
suspend fun screen() = coroutineScope {
val user = async { api.user() }
val feed = async { api.feed() }
Screen(user.await(), feed.await()) // parallel, not sequential
}
Cancellation, where people lose the point
Cancellation is cooperative. It works by throwing CancellationException at suspension points, so:
- A tight CPU loop with no suspension point never notices. Call
yield()or checkisActive. catch (e: Exception)swallows cancellation and breaks the whole mechanism. Rethrow it, or catch narrower.- Cleanup that must still run goes in
finally, and anything suspending there needswithContext(NonCancellable).
