Question
I am learning Kotlin coroutines and understand that they are built around suspend functions, but I am not fully clear on what suspend actually means.
The Kotlin documentation says:
Basically, coroutines are computations that can be suspended without blocking a thread.
I often hear the phrase suspending function, but it seems to me that it is really the coroutine that gets suspended while waiting for some work to finish. In normal English, suspend means to stop operating temporarily, so it sounds like the coroutine becomes idle.
Should we say that the coroutine is suspended? If so, which coroutine is suspended?
For example, the Kotlin documentation gives an example like this:
async {
// outer coroutine
val result = computation.await()
}
It says that await() is a suspending function that suspends a coroutine until some computation is done and returns its result.
Suppose computation() returns a Deferred<Boolean> like this:
fun computation(): Deferred<Boolean> {
return async {
true
}
}
In that case, when await() is called:
- does it suspend the outer coroutine,
- or the inner coroutine that performs the computation,
- or both?
Also, if a coroutine is suspended and a coroutine is often described as being like a lightweight thread, how can the computation still continue?
Is the correct understanding this:
- the outer coroutine calls
await() - the outer coroutine is suspended while waiting
- the underlying thread is released back to the thread pool instead of being blocked
- the inner coroutine continues running
- when the inner coroutine finishes, the outer coroutine resumes, possibly on a different thread
Is that what suspend means in Kotlin coroutines?
Short Answer
By the end of this page, you will understand that suspend in Kotlin does not mean “pause a thread.” It means a function may pause the current coroutine, save its state, and let the thread do other work. You will also learn which coroutine is suspended during calls like await(), how suspension differs from blocking, and why a suspended coroutine can later resume on the same or a different thread.
Concept
suspend in Kotlin marks a function that can pause and resume a coroutine without blocking the underlying thread.
The key idea is:
- A suspending function is a function that is allowed to suspend.
- A coroutine is the running computation whose execution can be paused.
- A thread is the actual OS/JVM thread that may be freed while the coroutine is paused.
So both phrases are used correctly, but they refer to different things:
suspend functiondescribes the function's capability.coroutine is suspendeddescribes what happens at runtime.
What suspend really means
A suspend function does not automatically pause. It simply has permission to pause the current coroutine at certain points.
For example:
suspend fun loadData(): String {
delay(1000)
return "done"
}
This function is marked suspend because it calls another suspending function, delay(). During delay(1000), the current coroutine may be suspended for 1 second without blocking a thread.
Suspension vs blocking
This is the most important distinction:
- Blocking: the thread waits and cannot do other work.
- Suspending: the coroutine waits, but the thread is free to run something else.
That is why coroutines are efficient.
Which coroutine is suspended in await()?
Consider:
val result = computation.await()
Here, await() suspends the coroutine that called await() if the result is not ready yet.
So in your example:
- the outer coroutine calls
await() - if
computationis still running, the outer coroutine is suspended - the inner coroutine that is producing the
Deferredresult keeps running - once the result is ready, the outer coroutine resumes
await() does not suspend the inner coroutine. The inner coroutine is the one being waited on.
Why this matters in real programming
This model is used everywhere in Kotlin:
- waiting for API responses
- reading from a database
- delaying retries
- processing streams of events
- coordinating concurrent tasks
Instead of blocking threads while waiting, coroutines suspend and resume efficiently. That makes asynchronous code easier to read and cheaper to run.
Mental Model
Think of a coroutine like a bookmark in a book.
- The coroutine is the reader's progress through the story.
- The thread is the chair the reader is sitting on.
- A
suspendfunction is a place where the reader is allowed to put in a bookmark and get up.
When suspension happens:
- the reader saves their place with a bookmark
- the chair becomes free for someone else
- later, the reader comes back
- they may sit in the same chair or a different one
- they continue from the bookmark
So:
- the coroutine is what pauses and resumes
- the thread is just where the coroutine happens to run at that moment
- the suspend function is a function that can create one of those bookmark points
For await() specifically:
- the outer coroutine reaches
await() - if the result is not ready, it puts down a bookmark and pauses
- the inner coroutine keeps working
- when the result is ready, the outer coroutine continues from the bookmark
Syntax and Examples
Core syntax
A suspending function is declared with the suspend keyword:
suspend fun fetchUser(): String {
delay(1000)
return "Alice"
}
You can only call a suspend function from:
- another
suspendfunction, or - a coroutine builder such as
launchorasync
Example: a suspending function
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
suspend fun greetLater(): String {
delay(500)
return "Hello"
}
fun main() = runBlocking {
val message = greetLater()
println(message)
}
What happens here?
greetLater()is markedsuspenddelay(500)suspends the current coroutine for half a second- the thread is not blocked during that delay
- after the delay, the coroutine resumes and returns
"Hello"
Example: await() suspends the caller
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val deferred = async {
delay(1000)
42
}
println("Waiting for result...")
val result = deferred.await()
println("Result: $result")
}
Explanation
async { ... }starts a coroutine and returns aDeferred<Int>- that inner coroutine computes the value
42 await()is called by the outer coroutine insiderunBlocking- if the value is not ready,
await()suspends the outer coroutine - the inner coroutine continues running
- when the inner coroutine completes,
await()returns the result
Important rule
suspend does not mean “run on another thread.”
It only means the function may suspend the current coroutine. Thread switching depends on the dispatcher and runtime context.
Step by Step Execution
Consider this example:
import kotlinx.coroutines.*
fun main() = runBlocking {
val deferred = async {
println("Inner: start")
delay(1000)
println("Inner: done")
10
}
println("Outer: before await")
val result = deferred.await()
println("Outer: after await = $result")
}
Step-by-step trace
1. runBlocking starts
fun main() = runBlocking {
- A coroutine starts.
- In this example,
runBlockingblocks the main thread until its work is finished.
2. async launches the inner coroutine
val deferred = async {
- A new coroutine starts.
- It will eventually produce a value and store it in
deferred.
3. Inner coroutine begins
println("Inner: start")
delay(1000)
- The inner coroutine prints
Inner: start. - Then it calls
delay(1000). - At that point, the inner coroutine is suspended for 1 second.
- The thread is not blocked by
delay().
4. Outer coroutine reaches await()
println("Outer: before await")
val result = deferred.await()
- The outer coroutine prints
Outer: before await. - It calls
await(). - Since the inner coroutine has not finished yet, the outer coroutine is suspended.
5. Both coroutines are now waiting for different reasons
At this moment:
- the inner coroutine is suspended because of
delay(1000) - the outer coroutine is suspended because it is waiting in
await()
This is possible because suspension is about coroutine state, not about blocking a thread.
6. Inner coroutine resumes
After 1 second:
println("Inner: done")
10
- The inner coroutine resumes.
- It prints
Inner: done. - It completes with value
10.
7. Outer coroutine resumes
Once deferred has its result:
println("Outer: after await = $result")
await()now returns10- the outer coroutine resumes
- it prints
Outer: after await = 10
Key takeaway
await() suspends the coroutine that calls it. It does not stop the coroutine being awaited; it waits for that coroutine's result.
Real World Use Cases
1. Waiting for an API call
suspend fun loadUserProfile(): User {
return api.getUser()
}
While waiting for the network response, the coroutine can suspend instead of blocking a thread.
2. Waiting for database results
suspend fun findOrder(id: Long): Order? {
return orderRepository.findById(id)
}
Database libraries with coroutine support use suspending functions so server threads are not wasted while I/O is in progress.
3. Coordinating concurrent work
val profile = async { loadProfile() }
val posts = async { loadPosts() }
val userProfile = profile.await()
val userPosts = posts.await()
One coroutine can wait for others to finish using await().
4. Retrying after a delay
suspend fun retryLater() {
delay(2000)
}
This pauses the coroutine for 2 seconds without blocking a thread.
5. UI programming
In Android or desktop apps, suspending functions let you wait for background work without freezing the UI thread, as long as the work is dispatched correctly.
Real Codebase Usage
In real projects, developers use suspending functions as the standard way to represent asynchronous operations.
Common patterns
Guard clauses and validation
suspend fun loadUser(id: String): User {
require(id.isNotBlank()) { "id must not be blank" }
return api.fetchUser(id)
}
Validation happens immediately; suspension only happens if the function reaches a suspending call.
Early return
suspend fun maybeLoadUser(id: String?): User? {
if (id == null) return null
return api.fetchUser(id)
}
A suspend function can still return early like a normal function.
Concurrent async work
suspend fun loadDashboard(): Dashboard = coroutineScope {
val user = async { api.fetchUser() }
val notifications = async { api.fetchNotifications() }
Dashboard(user.await(), notifications.await())
}
Here, await() suspends the current coroutine until each result is ready.
Error handling
suspend fun loadSafe(): Result<String> {
return try {
Result.success(api.fetchMessage())
} catch (e: Exception) {
Result.failure(e)
}
}
Suspending functions often wrap network or database calls that may fail.
Structured concurrency
Developers usually prefer coroutineScope, launch, and async so child coroutines are tied to the parent scope. This makes cancellation and cleanup safer and more predictable.
Common Mistakes
1. Thinking suspend means “runs on another thread”
Broken assumption:
suspend fun doWork() {
// This is not automatically background work
}
Why it is wrong:
suspendonly means the function may suspend- it says nothing by itself about thread choice
How to avoid it:
- use the appropriate dispatcher when needed
- remember that suspension and threading are related but different concepts
2. Thinking a suspending function always suspends
suspend fun immediateValue(): Int {
return 5
}
This is valid. A suspend function may never actually suspend during a particular call.
3. Thinking await() suspends the producer coroutine
Broken understanding:
val deferred = async { 42 }
val x = deferred.await() // some beginners think this pauses the async block
Correct idea:
await()suspends the caller if needed- the
asynccoroutine producing the value keeps running
4. Mixing up blocking and suspending
Blocking code:
Thread.sleep(1000)
Suspending code:
delay(1000)
Thread.sleep() blocks the thread.
delay() suspends the coroutine.
5. Calling a suspending function from a normal function
Broken code:
suspend fun fetch(): String = "ok"
fun main() {
val x = fetch() // error
}
Fix:
import kotlinx.coroutines.runBlocking
suspend fun fetch(): String = "ok"
fun main() = runBlocking {
val x = fetch()
println(x)
}
6. Assuming a resumed coroutine must continue on the same thread
A coroutine may resume on the same thread or a different one, depending on its dispatcher and context. Do not build logic that depends on thread identity unless that is explicitly guaranteed by the context.
Comparisons
| Concept | What pauses? | Does the thread wait? | Typical example |
|---|---|---|---|
| Blocking | The thread | Yes | Thread.sleep(1000) |
| Suspending | The coroutine | No | delay(1000) |
| Term | Meaning |
|---|---|
suspend fun | A function that is allowed to suspend the current coroutine |
| Suspended coroutine | A coroutine whose execution is paused and can resume later |
await() | A suspending call that waits for a Deferred result |
async | Starts a coroutine that returns a future-like Deferred<T> |
delay() vs Thread.sleep()
| Feature | delay() | Thread.sleep() |
|---|---|---|
| Blocks thread | No | Yes |
| Suspends coroutine | Yes | No |
Can be called in suspend code | Yes | Yes, but usually should be avoided there |
| Good for coroutine-based async code | Yes | Usually no |
launch vs async
| Builder | Returns | Used for |
|---|---|---|
launch | Job | Fire-and-forget work |
async | Deferred<T> | Concurrent work that produces a result |
Important wording
Both of these are correct, but they mean different things:
- “
await()is a suspending function.” - “The calling coroutine is suspended at
await().”
Cheat Sheet
suspend fun myFunction() {
delay(1000)
}
Quick rules
suspendmeans a function may suspend the current coroutine.suspenddoes not mean the function creates a new thread.suspenddoes not mean the function always suspends.- A suspending function can only be called from:
- another
suspendfunction - a coroutine builder like
launch,async, orrunBlocking
- another
await()
val deferred = async { 42 }
val result = deferred.await()
asyncstarts a coroutine that produces a resultawait()waits for that result- if the result is not ready,
await()suspends the caller - the producer coroutine continues running
Suspension vs blocking
delay(1000) // suspends coroutine
Thread.sleep(1000) // blocks thread
Thread behavior
- when a coroutine suspends, the thread can be used for other work
- when the coroutine resumes, it may continue on the same or a different thread
- the dispatcher influences where it resumes
Best mental sentence
A suspending function is a function that can pause the current coroutine without blocking the current thread.
FAQ
What does suspend mean in Kotlin?
It means the function is allowed to pause the current coroutine and resume later without blocking the underlying thread.
Does a suspend function always suspend?
No. It may suspend, but it can also run straight through and return immediately.
Does suspend create a new thread?
No. Thread creation and thread selection are separate from the suspend keyword.
Which coroutine is suspended by await()?
The coroutine that calls await() is suspended if the result is not ready yet.
Does await() stop the async coroutine that is computing the result?
No. The async coroutine continues running. await() only waits for its result.
Can a suspended coroutine resume on another thread?
Yes. Depending on the dispatcher and runtime context, it may resume on the same or a different thread.
What is the difference between delay() and Thread.sleep()?
delay() suspends a coroutine without blocking a thread. Thread.sleep() blocks the thread.
Why are suspending functions useful?
They let you write asynchronous code in a sequential style while using threads efficiently.
Mini Project
Description
Build a small Kotlin program that starts one coroutine to calculate a value and another coroutine that waits for that result using await(). This demonstrates the exact idea behind suspend: the waiting coroutine pauses without blocking a thread, while the worker coroutine continues and eventually provides the result.
Goal
Create a runnable example that shows await() suspending the caller and resuming it when the async computation finishes.
Requirements
- Start an outer coroutine using
runBlocking. - Launch an inner coroutine with
asyncthat waits briefly and returns a value. - Print messages before and after calling
await(). - Use
delay()inside the inner coroutine. - Show from the output that the outer coroutine waits for the inner result.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.