Question

What `suspend` Means in Kotlin Coroutines

kotlinasync-awaitkotlin-coroutinescoroutinesuspend

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 function describes the function's capability.
  • coroutine is suspended describes 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 computation is still running, the outer coroutine is suspended
  • the inner coroutine that is producing the Deferred result 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 suspend function 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
Take Quiz

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 suspend function, or
  • a coroutine builder such as launch or async

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 marked suspend
  • delay(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 a Deferred<Int>
  • that inner coroutine computes the value 42
  • await() is called by the outer coroutine inside runBlocking
  • 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, runBlocking blocks 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 returns 10
  • 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.

Take Quiz

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:

  • suspend only 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 async coroutine 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

ConceptWhat pauses?Does the thread wait?Typical example
BlockingThe threadYesThread.sleep(1000)
SuspendingThe coroutineNodelay(1000)
TermMeaning
suspend funA function that is allowed to suspend the current coroutine
Suspended coroutineA coroutine whose execution is paused and can resume later
await()A suspending call that waits for a Deferred result
asyncStarts a coroutine that returns a future-like Deferred<T>

delay() vs Thread.sleep()

Featuredelay()Thread.sleep()
Blocks threadNoYes
Suspends coroutineYesNo
Can be called in suspend codeYesYes, but usually should be avoided there
Good for coroutine-based async codeYesUsually no

launch vs async

BuilderReturnsUsed for
launchJobFire-and-forget work
asyncDeferred<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().”
Take Quiz

Cheat Sheet

suspend fun myFunction() {
    delay(1000)
}

Quick rules

  • suspend means a function may suspend the current coroutine.
  • suspend does not mean the function creates a new thread.
  • suspend does not mean the function always suspends.
  • A suspending function can only be called from:
    • another suspend function
    • a coroutine builder like launch, async, or runBlocking

await()

val deferred = async { 42 }
val result = deferred.await()
  • async starts a coroutine that produces a result
  • await() 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.

Take Quiz

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

  1. Start an outer coroutine using runBlocking.
  2. Launch an inner coroutine with async that waits briefly and returns a value.
  3. Print messages before and after calling await().
  4. Use delay() inside the inner coroutine.
  5. Show from the output that the outer coroutine waits for the inner result.
Take Quiz

Keep learning

Related questions

View more