How does return-type inference work for functions and properties, and when should you annotate the return type even though the compiler could infer it?
answer
- Expression body (=) infers; block body ({}) does not
- Block body returning a value needs an explicit type
- Recursive/abstract/explicitApi -> annotation required
- Annotate public APIs for stability + no leaks
- Explicit return type improves error locality
basics
~20 sA function written with = (an expression body) can have its return type guessed from the result. Block functions with { } cannot. Even when inference works, write the return type for public functions so callers aren't surprised by refactors.
solid answer
~50 sAn **expression-body** function (`fun f() = expr`) can infer its return type from the expression, just like a `val` infers from its initializer. A **block-body** function (`fun f() { ... }`) cannot infer a non-`Unit` return type — if it returns a value you must annotate it; otherwise its type is `Unit`. The same applies to a property with a getter/initializer. You **must** annotate when the body is recursive, abstract, or governed by `explicitApi()`. You **should** annotate, even when not required, on **public/protected API members**: an inferred return type couples the published type to the current implementation, so changing the body can silently change the API and break callers (or leak an internal type). Annotating also improves readability and IDE/compiler error locality — a wrong `return` is reported at the offending line rather than as a downstream type mismatch. For local helpers, inferred returns are usually fine and idiomatic.
code
kotlin · 6 linesfun double(x: Int) = x * 2 // inferred Int (fine: simple)
fun parse(s: String): List<String> = // annotate: public contract
s.split(",").map { it.trim() }
fun fib(n: Int): Int = // recursive: required
if (n < 2) n else fib(n - 1) + fib(n - 2)
fun log(m: String) { println(m) } // block body, no value -> Unitgo deeper
Knows fun f() = x * 2 can omit the return type.
Distinguishes expression-body from block-body inference and the Unit default.
Explains why public APIs should annotate returns (stability, leak prevention, error locality).
Recommends explicitApi() and codifies an annotate-public/infer-local convention with rationale about blast radius.
## Expression body vs block body ```kotlin fun double(x: Int) = x * 2 // expression body -> return type inferred: Int fun greet() = "hi" // inferred String fun double2(x: Int): Int { // block body -> must declare return type return x * 2 } fun log(msg: String) { println(msg) } // block body, no value -> Unit ``` - **Expression body** (`= expr`): return type inferred from `expr`. Omit it for concise locals. - **Block body** (`{ ... }`): a value-returning block **requires** an explicit return type; without one the function is `Unit`. ## Properties ```kotlin val area get() = width * height // inferred Int val name: String get() = field // annotate if part of an API or non-obvious ``` A property with an initializer or an expression-body getter infers its type the same way. ## When annotation is REQUIRED - **Recursive** expression body: `fun fib(n: Int): Int = if (n<2) n else fib(n-1)+fib(n-2)` — without `: Int` the compiler can't compute the type. - **Abstract / interface** members: no body to infer from. - **Explicit API mode** (`explicitApi()`): all public/protected members must declare types. ## When you SHOULD annotate (even if optional) ```kotlin // Risky: published type follows the implementation fun parseIds(s: String) = s.split(",").map { it.trim() } // inferred List<String> // Stable: callers depend on the declared type, not the body fun parseIds(s: String): List<String> = s.split(",").map { it.trim() } ``` Reasons: - **API stability:** an inferred return type is whatever the body currently produces; a refactor can change it (e.g. `List` → `Sequence`, or a public type → an internal one) and break callers. - **Leak prevention:** inference can expose an internal/return type you didn't mean to publish. - **Readability:** readers see the contract without tracing the body. - **Error locality:** with an explicit return type, a mistaken `return` is flagged at the function; with inference, the error often appears at the *call site* as a confusing type mismatch. ## When inference is fine For **private/local** helpers, short expression bodies, and obvious results, inferred returns are idiomatic and keep code concise. The guidance is proportional: tighten as visibility and blast radius grow. ## Summary heuristic - Local/private + obvious → infer. - Public/protected, non-obvious, or recursive/abstract → annotate (and consider `explicitApi()` to enforce it project-wide).
- Does `fun f() { 42 }` return Int?No. A block body with no `return` statement returns `Unit`; the `42` is just an unused expression.
- Why annotate return types on library functions?To keep the published type stable across refactors and avoid leaking internal types — an inferred type silently follows the implementation.
- Can a property getter infer its type?Yes, an expression-body getter (`val x get() = ...`) infers like an expression-body function, but annotate it for public APIs.
saying these in an interview costs you the question
- Thinking block-body functions infer non-Unit return types
- Always relying on inferred return types in public APIs
- Not knowing recursion forces an explicit return type
- Assuming `{ 42 }` returns Int
- Treating expression and block bodies as equivalent for inference