suspend function: what it is, syntax, and how it works in coroutines

Author: IT Sectr Published: 2026-06-22 Reading time: 9 min

Suspend function is a function with the suspend modifier that can pause its execution without blocking a thread and resume later in the same coroutine. According to JetBrains Kotlin Docs, 2025, suspend functions are a fundamental building block of coroutines, providing asynchrony without callbacks. Each suspend function is compiled into a state machine based on Continuation, allowing efficient management of suspension points.

Key Takeaways

  • Suspend — a Kotlin keyword that marks a function as pausable (asynchronous)
  • Continuation — a hidden parameter that the compiler adds to every suspend function to save state
  • Suspension points — places where other suspend functions are called, where a coroutine can stop without blocking
  • State machine — the internal representation of a suspend function, where each suspension point is a separate state
  • Coroutine-only call — suspend functions can only be called from another suspend function or from launch/async

What is a suspend function in Kotlin?

Suspend function is a function declared with the suspend keyword that can pause execution at one or more points without blocking the thread. Each call to a suspend function within another suspend function is a potential suspension point.

kotlin
suspend fun fetchUserData(): User {
    val response = httpClient.get("/user")
    return parser.parse(response)
}

The Kotlin compiler translates such a function into a state machine. Each suspension point (a call to another suspend function) becomes a state (label). The current thread is released between states, and after the awaited operation completes, execution resumes from the next state.

History

Suspend functions appeared in Kotlin 1.3 (2018) along with coroutines as an experimental feature and became stable in Kotlin 1.5 (2021). Before that, asynchrony in Kotlin/Java was achieved through callbacks, RxJava, and CompletableFuture. Suspend functions offered an alternative with linear syntax and automatic thread management.

How suspend functions work: Continuation and state machine

Understanding the inner workings of suspend functions is the key to working correctly with coroutines. Unlike regular functions, each suspend function is compiled into a class with the Continuation interface.

Continuation — the hidden parameter

The Kotlin compiler adds a Continuation-type parameter at the end of each suspend function's parameter list. Continuation contains:

  • context — CoroutineContext (dispatcher, job, context elements)
  • resumeWith — method to resume execution with a result or exception
  • label — index of the current state in the state machine

State machine example

Suppose we have a suspend function with two calls to other suspend functions:

kotlin
suspend fun process() {
    val a = stepOne()
    val b = stepTwo(a)
    println(b)
}

The compiler transforms it into a state machine with labels:

kotlin
// Simplified generated code representation
fun process(cont: Continuation<Unit>): Any? {
    val cont = cont as ProcessContinuation
    when (cont.label) {
        0 -> {
            cont.label = 1
            if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        }
        1 -> {
            cont.label = 2
            val a = cont.result as TypeA
            if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
        }
        2 -> {
            println(cont.result)
            Unit
        }
    }
}

Key observation: if the function returns COROUTINE_SUSPENDED, the current thread is released. When the async operation completes, Continuation.resumeWith is called, and the state machine continues from the next label.

Suspend function syntax: declaration and invocation

Declaring a suspend function is no different from a regular one, except for the suspend keyword before fun. There is only one restriction: a suspend function can only be called from a coroutine or another suspend function.

Basic declaration

kotlin
suspend fun delayAndReturn(ms: Long): String {
    delay(ms)
    return "Done after ${ms}ms"
}

In this example, delay is also a suspend function that suspends the coroutine for the specified number of milliseconds without blocking the thread. After the delay, execution resumes.

Calling from a coroutine

kotlin
fun main() = runBlocking {
    val result = delayAndReturn(1000)
    println(result)
}

runBlocking creates a bridge between the regular world and coroutines. Inside the lambda, any suspend functions can be called.

Suspend lambdas and functional types

Kotlin supports suspend versions of functional types — suspend () -> T and suspend (A) -> B. This allows passing async lambdas to higher-order functions:

kotlin
suspend fun  withRetry(
    retries: Int = 3,
    block: suspend () -> T
): T {
    repeat(retries - 1) {
        try { return block() }
        catch (_: Exception) { delay(100) }
    }
    return block()
}

The withRetry function takes a suspend lambda and retries its execution on errors. This is a typical pattern for network requests with retries.

How suspend functions differ from regular ones

The differences between suspend and regular functions go beyond simply adding a modifier. Let's look at the key distinctions.

CharacteristicRegular functionSuspend function
Execution threadBlocks the thread until completionCan release the thread and resume later
Compiler parametersOnly specified parametersImplicit Continuation at the end
Call from regular functionYesNo
StackPhysical thread stackState machine in heap + physical stack between points
Return valueDirect valueValue or COROUTINE_SUSPENDED
PerformanceMinimal overhead~a few nanoseconds per state machine (Kotlin 1.9+)

Why suspend functions cannot be called from regular ones

A regular function does not have a Continuation — it has nowhere to save state and nothing to resume execution with. If you need to call a suspend function from a regular one, use runBlocking (for tests) or CoroutineScope.launch (for production with lifecycle awareness).

Suspend function examples in Android

Let's look at three real-world scenarios for using suspend functions in Android apps with Kotlin.

Example 1: Room DAO with suspend queries

Room supports suspend functions directly — the query is executed on a background thread automatically:

kotlin
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getUser(id: Int): User?

    @Insert
    suspend fun insertUser(user: User)
}

Room internally uses Dispatchers.IO to execute the query, and the result is returned on the dispatcher where the suspend function was called.

Example 2: Composing suspend functions for screen loading

kotlin
class ProfileViewModel : ViewModel() {
    private val repo = ProfileRepository()

    fun loadProfile(id: String) {
        viewModelScope.launch {
            val profile = repo.getProfile(id)
            _profile.value = profile
        }
    }
}

ViewModelScope.launch creates a coroutine, inside which the getProfile suspend function is called. After getting the result, the UI is updated on the main thread.

Example 3: Sequential async steps

kotlin
suspend fun placeOrder(cart: Cart): OrderResult {
    val validated = validateCart(cart)
    val payment = processPayment(validated)
    val receipt = sendReceipt(payment)
    return receipt
}

Three suspend functions execute sequentially. At each step, the coroutine can suspend without blocking the thread. If any step throws an exception, the remaining steps do not execute, protecting against incorrect order states.

Common mistakes when working with suspend functions

Even experienced Kotlin developers make mistakes when designing suspend functions. Let's look at the most common ones.

Mistake 1: Blocking calls inside suspend

A suspend function does not automatically make code asynchronous. Thread.sleep(), InputStream.read(), and other blocking calls will still block the thread. Use withContext(Dispatchers.IO) to wrap blocking operations.

Mistake 2: Creating suspend functions without necessity

If a function doesn't call other suspend functions and doesn't perform async operations — the suspend modifier is redundant. It adds overhead for the state machine and restricts the calling context. Only make a function suspend when it actually suspends.

Mistake 3: Ignoring CancellationException

When a coroutine is cancelled, suspend functions throw CancellationException. Don't catch it mindlessly — you deprive the calling code of the ability to properly complete cancellation. If you need to perform a finalizing operation, use a finally block and NonCancellable.

kotlin
suspend fun safeOperation() {
    try {
        doWork()
    } finally {
        withContext(NonCancellable) {
            cleanup()
        }
    }
}

The finally block always executes, including on cancellation, and NonCancellable ensures the cleanup won't be interrupted.

Mistake 4: Calling suspend functions from callbacks

You cannot call a suspend function directly from a callback without creating a coroutine. Use suspendCoroutine or suspendCancellableCoroutine to adapt callback style to coroutines.

Frequently Asked Questions

Can a suspend function have no suspension points?

Yes, technically a suspend function can not call other suspend functions. The compiler will create a state machine with a single state (label 0). However, there is no practical benefit in such a function — it executes like a regular one but with overhead. Do not use suspend unnecessarily.

How to debug suspend functions?

Kotlin provides kotlinx-coroutines-debug — a library with DebugProbes and coroutine tracing tools. In Android Studio starting from Arctic Fox, there is a built-in Coroutines tab in the Debugger that shows active coroutines, their state, and suspension points.

Does the number of suspend points affect performance?

Each suspension point creates a new state in the state machine. For most applications, the overhead of one point is a few nanoseconds (Kotlin 1.9+). Only with tens of thousands of points in a loop should you consider combining operations or using sequence/flow.

How is a suspend function different from async/await in other languages?

In Kotlin, suspend is a function type modifier, not a return value marker (like async in C#). Any suspend function can have any parameters and return type, and its call syntactically does not differ from a regular function call — there is no await operator at the call site.

How to convert a callback function to suspend?

Use suspendCancellableCoroutine for adaptation. Inside, you register a callback that calls continuation.resume(), and return a cancellation token if the callback supports unsubscription. This is the standard pattern for wrapping old Android APIs.

Summary

  • Suspend function — a function with the suspend modifier that can pause execution without blocking a thread via the Continuation mechanism
  • State machine — the internal representation of a suspend function in Kotlin bytecode, where each suspension point is a separate state with a label
  • Continuation — a hidden parameter containing the coroutine context and the resumeWith method for resuming execution
  • Coroutine-only call — suspend functions are not accessible from regular functions without runBlocking or CoroutineScope
  • Blocking operations inside suspend require withContext(Dispatchers.IO) — otherwise the thread blocks
  • Room and Retrofit natively support suspend functions, automatically managing background threads
  • CancellationException — handle cancellation via finally + NonCancellable, do not catch CancellationException mindlessly

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also