What is a suspend function type such as `suspend () -> T`, and where can a value of that type be invoked?
answer
- `suspend` is part of the type
- Invoke only from suspend fun or coroutine builder
- CPS adds hidden Continuation param
- `suspend () -> T` ≠ `() -> T` (not assignable)
- Lambda passed is a suspend lambda → can call delay
basics
~20 sIt 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 linessuspend 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
Recognizes that a suspend function type must be called from a coroutine/suspend context and not from normal code.
Explains suspend is part of the type, that it's incompatible with plain function types, and how to bridge with builders.
Describes the CPS transform / hidden Continuation parameter as the reason for the ABI difference and non-assignability.
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