Explanation
What just happened
runBlocking ran both coroutines on one thread, main. Coroutine a printed a step, then delay() suspended it. A suspended coroutine holds no thread, so b ran its step, then suspended too. They took turns at every delay(): a1 b1 a2 b2 a3 b3, all on main.
Try the what-if switch
- Thread.sleep():
Thread.sleepdoesn’t suspend. It blocksmainitself, sobcan’t run untilahas done all three steps: a1 a2 a3 b1 b2 b3.
Why it matters
Coroutines are cheap because many of them share a few threads. That only works if they suspend instead of blocking. One blocking call holds a thread that other coroutines were counting on.
Watch out
Blocking calls don’t look different from suspending ones in the code. Calls like JDBC, file reads and Thread.sleep block; run them on Dispatchers.IO.
Common questions
Do Kotlin coroutines run on separate threads?
Not necessarily. In runBlocking, both coroutines here ran on the main thread, taking turns. Which threads run a coroutine depends on its dispatcher.
What's the difference between delay() and Thread.sleep() in a coroutine?
delay() suspends the coroutine and frees its thread for other coroutines. Thread.sleep() blocks the thread itself, so no other coroutine can run on it until it returns.
When does a coroutine give up its thread?
At a suspension point, like delay(), join() or await(). Between two suspension points, a coroutine keeps its thread.
Verified: this timeline matches a real program, checked in CI on Kotlin 2.4 with kotlinx.coroutines 1.11. How we verify. Unclear or wrong? Tell us