skip to content

What is the difference between `delay()` and `Thread.sleep()` inside a coroutine?

level: middleimportance: must knowfreq 80%

answer

  1. delay suspends the coroutine; sleep blocks the thread
  2. delay is cancellable; sleep is not
  3. sleep starves the dispatcher's thread pool
  4. delay is a suspend fun; Thread.sleep is plain JVM
  5. Blocking legacy code -> isolate on Dispatchers.IO

basics

~10 s

delay() 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 lines
kotlin
import 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

for a junior

States delay is non-blocking and Thread.sleep blocks the thread.

for a middle

Explains thread-pool starvation and that delay is cancellable while sleep is not.

for a senior

Discusses dispatcher sizing, isolating blocking calls on Dispatchers.IO, and cancellation semantics precisely.

for a principal

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

context