When designing generic APIs and Java interop, when should you pick Unit as a type argument, and what pitfalls arise (e.g. Unit vs Void, suspend functions, Continuation)?
answer
- Unit = 'no payload' generic arg (Result<Unit>, Channel<Unit>)
- Beats Void (null-only) and Object (lies)
- Java impl of () -> Unit returns Unit.INSTANCE
- Unit != java.lang.Void; Callable<Void> wants null
- suspend Unit -> Continuation<Unit>, COROUTINE_SUSPENDED
basics
~20 sUse Unit as a generic argument when an API needs a type but there's no meaningful payload, like a result that only signals success. Watch out for Java interop where Unit maps to kotlin.Unit, not java.lang.Void, and for suspend functions whose Unit result still flows through Continuation.
solid answer
~40 sUnit is the natural 'no payload' type argument: `Result<Unit>`, `Channel<Unit>` for signalling, `Deferred<Unit>`, or a callback `(Result) -> Unit`. It beats `Void`/`Object` because it has an actual value (`Unit`) you can pass, whereas `Void` forces `null`. Pitfalls: (1) Java sees a Kotlin `() -> Unit` as `Function1<..., kotlin.Unit>`, and a Java lambda must `return kotlin.Unit.INSTANCE`. (2) Mapping a Kotlin `Unit`-returning method into a Java `Callable<Void>` requires returning `null`, not Unit. (3) A `suspend fun f(): Unit` compiles with a `Continuation<Unit>` parameter and returns `Unit.INSTANCE` or the `COROUTINE_SUSPENDED` marker; bridging to Java reactive types needs care. (4) Don't reach for `Nothing` when you mean 'completes with no value' -- that would mean 'never completes'. The decision rule: Unit when the operation completes and you simply have nothing to hand back.
code
kotlin · 10 lines// 'No payload' generic positions
val signal = Channel<Unit>() // event stream with no data
val done = CompletableDeferred<Unit>() // completes to signal readiness
fun process(): Result<Unit> = runCatching { /* side effects */ }
// Interop: Java implementing this must `return Unit.INSTANCE;`
val cb: () -> Unit = { /* ... */ }
// suspend Unit compiles with a Continuation<Unit>
suspend fun flush(): Unit { /* ... */ }go deeper
Recognizes Unit can be a generic argument but may not know when to choose it.
Chooses Unit for simple 'no payload' results like Result<Unit> and explains it beats Void.
Handles Java interop (Unit.INSTANCE, Void mismatch) and knows suspend Unit involves a Continuation.
Designs APIs deliberately distinguishing Unit/Nothing/Void, anticipating interop and coroutine bridging pitfalls.
## When Unit is the right type argument Reach for `Unit` whenever a **generic slot demands a type but there is no payload**: ```kotlin val ready: CompletableDeferred<Unit> = CompletableDeferred() // signal-only val ticks = Channel<Unit>() // pure event stream fun runJob(): Result<Unit> = runCatching { doWork() } // success/failure, no value val onDone: (Unit) -> Unit = { /* ... */ } ``` It is preferable to `java.lang.Void` (whose only value is `null`, forcing `Result<Void>` to carry `null`) and to `Any?`/`Object` (which lie about the payload). Unit has a **real, passable value**. ## Java interop pitfalls - A Kotlin `() -> Unit` surfaces to Java as `kotlin.jvm.functions.Function0<kotlin.Unit>`; a hand-written Java implementation must `return kotlin.Unit.INSTANCE;`. Kotlin's SAM conversions and `fun interface`s hide this from Kotlin callers but not from Java. - Bridging to `java.util.concurrent.Callable<Void>` or `Supplier<Void>` is **not** the same as Unit: those expect `null`. Don't try to return `Unit.INSTANCE` where `Void` is expected. - `kotlin.Unit` is **not** assignable to `java.lang.Void` and vice versa; they are unrelated types despite both meaning 'no value'. ## Suspend functions and Continuation A `suspend fun f(): Unit` compiles to a JVM method taking an extra `Continuation<Unit>` parameter and returning `Object` -- either `Unit.INSTANCE` (completed) or the internal `COROUTINE_SUSPENDED` sentinel. When integrating with callback/reactive Java APIs (`CompletableFuture<Void>`, RxJava `Completable`), you bridge Unit completion to the Java 'no value' convention manually (e.g. complete the future with `null`). ```kotlin suspend fun save(): Unit { /* ... */ } // JVM: Object save(Continuation<? super Unit> cont) ``` ## Unit vs Nothing in API design - `Unit`: the operation **completes** with nothing to return. Correct for 'fire-and-forget' results. - `Nothing`: the operation **never returns** (throw-helpers, infinite producers). Using `Nothing` where you meant Unit would wrongly mark callers' downstream code unreachable. ## Decision rule Use Unit when: a type parameter is mandatory, the work finishes, and there's simply no value to hand back. Use a real type for an actual payload; use Nothing only for non-termination. ## Keywords/APIs `Result<Unit>`, `Channel<Unit>`, `CompletableDeferred<Unit>`, `Function0<Unit>`, `Unit.INSTANCE`, `java.lang.Void`, `Continuation<Unit>`, `COROUTINE_SUSPENDED`, `Completable`/`CompletableFuture<Void>`.
- Why not use java.lang.Void instead of Unit for a 'no result' generic in Kotlin?Void has only the value null, so you'd be passing null around; Unit has a real singleton value and reads more clearly.
- What does a suspend function returning Unit actually return at the JVM level?Either kotlin.Unit.INSTANCE when it completes, or the internal COROUTINE_SUSPENDED marker when it suspends; the method's declared JVM return is Object.
- Should you use Nothing instead of Unit for a function that 'returns no value'?No -- Nothing means it never returns at all; Unit means it completes with no useful value.
saying these in an interview costs you the question
- Treating kotlin.Unit and java.lang.Void as interchangeable
- Using Nothing where Unit is meant (implying non-termination)
- Returning Unit.INSTANCE where a Java API expects null (Callable<Void>)
- Claiming suspend Unit functions have no Continuation parameter
- Picking Object/Any? for 'no payload' instead of Unit