skip to content

What is a suspend function type such as `suspend () -> T`, and where can a value of that type be invoked?

level: middleimportance: must knowfreq 60%

answer

  1. `suspend` is part of the type
  2. Invoke only from suspend fun or coroutine builder
  3. CPS adds hidden Continuation param
  4. `suspend () -> T` ≠ `() -> T` (not assignable)
  5. Lambda passed is a suspend lambda → can call delay

basics

~20 s

It is the type of a function that can suspend, like a lambda you can pause and resume. You can only call it from inside a coroutine or another suspend function, not from ordinary code.

solid answer

~40 s

`suspend () -> T` is a **suspend function type** — a first-class value (lambda or reference) whose invocation may suspend the calling coroutine. Like any function type it can be stored, passed, and returned, but it carries the `suspend` modifier in its type, so it can be invoked **only from a suspend context**: inside another `suspend` function or a coroutine builder body (`launch`, `async`, `runBlocking`, `coroutineScope`). Calling it from normal code is a compile error. Under the hood the compiler adds a hidden `Continuation` parameter (CPS transform), which is why the type is not assignment-compatible with a plain `() -> T`. A lambda passed to a parameter of suspend-function type is itself a suspend lambda, so it may call other suspend functions like `delay` inside it.

code

kotlin · 11 lines
kotlin
suspend fun <T> retry(times: Int, block: suspend () -> T): T {
    repeat(times - 1) {
        try { return block() } catch (e: Exception) { /* retry */ }
    }
    return block()
}

suspend fun fetch(): String { delay(50); return "data" }

// usage inside a coroutine
// runBlocking { println(retry(3) { fetch() }) }

go deeper

for a junior

Recognizes that a suspend function type must be called from a coroutine/suspend context and not from normal code.

for a middle

Explains suspend is part of the type, that it's incompatible with plain function types, and how to bridge with builders.

for a senior

Describes the CPS transform / hidden Continuation parameter as the reason for the ABI difference and non-assignability.

for a principal

Reasons about API design with higher-order suspend params (e.g. inline + crossinline, structured concurrency, cancellation propagation through the lambda).

## Plain vs suspend function types A **function type** is the type of a callable value: `() -> T`, `(Int) -> String`, etc. Prefixing it with the **`suspend`** keyword gives a **suspend function type**: `suspend () -> T`, `suspend (Int) -> String`. The `suspend` modifier is part of the type. Semantically it means: *invoking this value may suspend* — pause the current coroutine without blocking the thread, and resume later. ## Where it can be invoked A value of suspend function type can be **created and passed around anywhere**, but it can only be **invoked** from a **suspend context**: - inside another `suspend` function, or - inside a coroutine builder lambda (`launch { }`, `async { }`, `runBlocking { }`, `coroutineScope { }`, `flow { }`). Calling it from normal (non-suspend) code is a compile error: *"Suspend function should be called only from a coroutine or another suspend function."* ```kotlin suspend fun runTwice(block: suspend () -> Unit) { block() // OK — runTwice is suspend block() } fun wrong(block: suspend () -> Unit) { block() // COMPILE ERROR — wrong() is not suspend } ``` ## Why it is incompatible with `() -> T` The compiler transforms suspend functions using **continuation-passing style (CPS)**: it adds a hidden `Continuation<T>` parameter and returns either the value or the marker `COROUTINE_SUSPENDED`. So `suspend () -> T` and `() -> T` have **different runtime signatures** and are **not interchangeable** — you cannot pass a `suspend () -> T` where a `() -> T` is expected, or vice versa. ## Lambdas When a parameter type is `suspend () -> T`, the lambda you pass is automatically a **suspend lambda**, so inside it you may call `delay`, other suspend functions, etc.: ```kotlin runBlocking { runTwice { delay(100) // allowed: this lambda is a suspend lambda println("tick") } } ``` ## Interop helpers To bridge from non-suspend code you use a builder (`runBlocking { block() }`) or convert via `coroutineScope`/`async`. You never just call it from `main` without a builder.

  • Can you pass a `suspend () -> T` where a `() -> T` parameter is expected?
    No. They are distinct types with different ABIs (the suspend one has a hidden Continuation parameter). The compiler rejects the assignment.
  • How do you invoke a `suspend () -> Unit` from a regular `main` function?
    Wrap it in a coroutine builder, e.g. `runBlocking { block() }`, or make `main` itself `suspend fun main()`.

A suspend function type is a recipe that may say 'wait for the oven' — you can only follow it in a kitchen (coroutine) that knows how to wait.

saying these in an interview costs you the question

  • Saying a suspend function type can be invoked from any normal function
  • Claiming `suspend () -> T` is assignable to `() -> T`
  • Confusing suspending with blocking the thread
  • Not knowing a builder (runBlocking/launch) is needed to call it from non-suspend code
  • Thinking the `suspend` keyword is only on the declaration, not part of the type

context