skip to content

Can `invoke` be overloaded and can it be a suspend function? Show how multiple `invoke` overloads resolve and how a suspending callable object behaves.

level: seniorimportance: nice to knowfreq 25%

answer

  1. invoke uses normal overload resolution
  2. suspend operator fun invoke is legal
  3. UseCase pattern: useCase(params)
  4. SuspendFunctionN shape for suspend invoke
  5. watch for ambiguity with defaults/vararg

basics

~10 s

Yes. A type can declare several invoke overloads with different parameters, and the compiler picks the matching one. invoke can also be marked suspend, so calling obj(...) works inside a coroutine and can suspend.

solid answer

~40 s

`invoke` follows ordinary overload-resolution rules: you can declare `operator fun invoke()`, `operator fun invoke(x: Int)`, `operator fun invoke(x: String)`, etc., and `obj(arg)` resolves by argument types like any overloaded method — with the usual specificity/ambiguity rules. You can also write `suspend operator fun invoke(...)`, producing a callable object whose call point may suspend; this is common in clean-architecture 'UseCase' objects (`suspend operator fun invoke(params): Result`) invoked as `useCase(params)` inside a coroutine. Such a type corresponds to a `SuspendFunctionN` shape. Caveats: overloads must differ enough to avoid ambiguity; default arguments can collide with other overloads; and mixing suspend and non-suspend `invoke` overloads is allowed but the suspend one is only callable from a coroutine context. `vararg`, generics, and reified-free type parameters all work on `invoke` as on normal functions.

code

kotlin · 10 lines
kotlin
class GetOrders(private val repo: OrderRepo) {
    suspend operator fun invoke(userId: Long): List<Order> =
        repo.loadFor(userId)
}

// usage inside a coroutine
suspend fun show(getOrders: GetOrders, uid: Long) {
    val orders = getOrders(uid) // suspends; reads like a function call
    println(orders.size)
}

go deeper

for a junior

May know overloading exists but not that invoke supports suspend.

for a middle

Writes overloads and a basic suspend invoke, but may miss resolution subtleties.

for a senior

Explains overload resolution, suspend context rules, and the UseCase pattern crisply.

for a principal

Connects to architecture (DI, testability) and reasons about API ambiguity and coroutine context propagation.

## Overloading `invoke` `invoke` is resolved like any other member function, so a single type can expose several overloads: ```kotlin class Router { operator fun invoke() = "home" operator fun invoke(path: String) = "page:$path" operator fun invoke(id: Int) = "item:$id" } val r = Router() r() // "home" r("about") // "page:about" r(7) // "item:7" ``` The compiler uses standard overload resolution: pick the most specific applicable signature; report an error if the call is ambiguous. Default parameter values and `vararg` interact with this exactly as they do for normal functions, so two overloads that both match an empty or single-arg call can cause ambiguity. ## suspend `invoke` You can mark the operator `suspend`: ```kotlin class FetchUser(private val api: Api) { suspend operator fun invoke(id: Long): User = api.getUser(id) } // in a coroutine: val user = fetchUser(42L) // suspends here ``` This is the idiomatic 'UseCase/Interactor' pattern: the object is a single-responsibility callable, and `useCase(params)` reads like a function call while remaining injectable, testable, and stateful. A `suspend operator fun invoke` corresponds to the `SuspendFunctionN` interface family, so such objects line up with `suspend` function types. ## Mixing and resolution notes - You may have both `operator fun invoke(...)` and `suspend operator fun invoke(...)` overloads; the suspend one is only callable from a coroutine/`suspend` context. - Generic `invoke` (`operator fun <T> invoke(x: T)`) is allowed; type inference applies. - Ambiguity errors arise the same way as for normal overloaded methods — design signatures to be distinguishable. ## Why senior-relevant The pattern shows up in DI-driven architectures and DSLs. Knowing that `invoke` is just an operator-named method — subject to normal overloading, suspension, generics — lets you reason about resolution and coroutine context correctly rather than treating callable objects as magic.

  • Where can you call a `suspend operator fun invoke`?
    Only from a coroutine or another `suspend` function — the same restriction as any suspend function; `obj(...)` does not bypass coroutine context rules.
  • What can cause an overloaded `invoke` call to fail to compile?
    Ambiguity: if two overloads are equally applicable for the given arguments (often due to defaults or implicit conversions), the compiler reports an ambiguous-call error.

saying these in an interview costs you the question

  • Claiming `invoke` cannot be overloaded
  • Saying suspend `invoke` can be called outside a coroutine
  • Treating overload resolution for `invoke` as special/different
  • Thinking the UseCase pattern needs reflection
  • Ignoring ambiguity risks from default args

context