What is the difference between `delay()` and `Thread.sleep()` inside a coroutine?
answer
- delay suspends the coroutine; sleep blocks the thread
- delay is cancellable; sleep is not
- sleep starves the dispatcher's thread pool
- delay is a suspend fun; Thread.sleep is plain JVM
- Blocking legacy code -> isolate on Dispatchers.IO
basics
~10 sdelay() pauses the coroutine without blocking the thread, so other coroutines can use it. Thread.sleep() blocks the whole thread, freezing everything scheduled on it.
solid answer
~40 s`delay(timeMillis)` is a `suspend` function: it suspends the current coroutine and frees the underlying thread for other coroutines, resuming after the timeout. It is cooperative and cancellable — if the coroutine is cancelled during `delay`, it throws `CancellationException` promptly. `Thread.sleep(timeMillis)` is a blocking JVM call: it parks the actual thread, so no other coroutine dispatched on that thread can run, and it ignores coroutine cancellation. On a small dispatcher (e.g. `Dispatchers.Default` with N threads), a few `Thread.sleep` calls can starve all your coroutines, while thousands of `delay` calls coexist happily on one thread. Rule of thumb: inside coroutines always prefer `delay`; only use `Thread.sleep` for genuinely blocking legacy code, and isolate it on `Dispatchers.IO`.
code
kotlin · 9 linesimport kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
fun main() = runBlocking(Dispatchers.Default) {
val withDelay = measureTimeMillis {
coroutineScope { repeat(100) { launch { delay(500) } } }
}
println("delay: ~${withDelay}ms") // ~500ms, all suspend together
}go deeper
States delay is non-blocking and Thread.sleep blocks the thread.
Explains thread-pool starvation and that delay is cancellable while sleep is not.
Discusses dispatcher sizing, isolating blocking calls on Dispatchers.IO, and cancellation semantics precisely.
Reasons about throughput/latency trade-offs across dispatchers and sets team conventions for handling blocking code.
## The core distinction Both pause for a duration, but they pause **different things**: - **`delay(timeMillis)`** is a `suspend` function from `kotlinx.coroutines`. It **suspends the coroutine** and returns the thread to the pool. Other coroutines run on that freed thread during the wait. After the delay elapses, the coroutine is rescheduled and resumes. - **`Thread.sleep(timeMillis)`** is a plain JVM call that **blocks the OS thread**. While it sleeps, that thread does nothing and cannot serve any other coroutine assigned to it. ## Why it matters: thread starvation Dispatchers have a limited number of threads. `Dispatchers.Default` defaults to the number of CPU cores. Consider 4 threads: ```kotlin // Launches 1000 coroutines on Dispatchers.Default repeat(1000) { launch { Thread.sleep(1000) // BAD: blocks a real thread for 1s } } // Only 4 can "sleep" at once; the rest wait -> ~250s total repeat(1000) { launch { delay(1000) // GOOD: suspends, frees the thread } } // All 1000 suspend together -> ~1s total ``` ## Cancellation - `delay` is a **cancellable suspension point**: if the coroutine is cancelled while delaying, `delay` throws `CancellationException` immediately and the coroutine stops promptly. - `Thread.sleep` is **not cancellation-aware**: cancelling the coroutine does not interrupt the sleeping thread; it runs to completion (unless the thread is separately interrupted). ## When `Thread.sleep` is acceptable If you must call genuinely blocking, non-suspending code (a legacy driver, a blocking JDBC call), do it inside `withContext(Dispatchers.IO)` so it blocks an IO-pool thread designed to absorb blocking, not a CPU/Default thread. ```kotlin withContext(Dispatchers.IO) { legacyBlockingCall() // isolated on the IO dispatcher } ``` ## Summary table - Blocks thread? `delay` no, `Thread.sleep` yes. - Suspends coroutine? `delay` yes, `Thread.sleep` no (it blocks). - Cancellable? `delay` yes, `Thread.sleep` no. - Is it `suspend`? `delay` yes, `Thread.sleep` no.
- If `delay` doesn't block a thread, what actually wakes the coroutine up after the timeout?The coroutine machinery schedules a resumption on the dispatcher after the timeout (the event loop / a scheduled executor), then dispatches the continuation — no thread was held during the wait.
- Is `Thread.sleep` ever the right call inside coroutines?Only when wrapping genuinely blocking, non-suspendable code, and then you should run it on `Dispatchers.IO` to avoid starving CPU-bound dispatchers.
delay is putting your name on a waitlist and walking away so others use the table; Thread.sleep is sitting at the table doing nothing while everyone else waits.
saying these in an interview costs you the question
- Saying delay and Thread.sleep are interchangeable
- Claiming Thread.sleep respects coroutine cancellation
- Not knowing delay is non-blocking / suspending
- Putting Thread.sleep on Dispatchers.Default without concern
- Thinking delay blocks the thread for the duration