When you write `::println`, the function is overloaded many times. How does Kotlin decide which overload the reference points to, and what happens if it's ambiguous?
answer
- Expected type picks the overload
- No expected type + multiple fits = ambiguity error
- Fix: annotate type or use in typed context
- Resolution is compile-time, fixed in bytecode
- Same rule for members & constructors
basics
~20 sKotlin uses the surrounding context — the expected function type — to pick the matching overload. If nothing constrains the type and several overloads fit, it's ambiguous and the compiler reports an error until you add a type.
solid answer
~40 sA reference to an overloaded function is resolved by the **expected type** at the use site. If you assign `::println` to a `(Int) -> Unit`, the compiler selects the `println(Int)` overload; passing it to `forEach` on a `List<String>` selects `println(Any?)`/`println(String?)` as appropriate. When the context provides no expected functional type (e.g. `val x = ::println` with no annotation) and multiple overloads remain viable, resolution is **ambiguous** and you get a compile error like 'overload resolution ambiguity'. The fix is to supply an explicit type: `val x: (Int) -> Unit = ::println`, or in tricky cases cast/wrap in a lambda. Resolution is purely compile-time — the chosen overload is fixed in the bytecode.
code
kotlin · 8 linesfun main() {
val asInt: (Int) -> Unit = ::println // println(Int)
asInt(42)
listOf("x", "y").forEach(::println) // println(Any?)
// val ambiguous = ::println // compile error: overload ambiguity
}go deeper
May only know ::println 'works in forEach' without explaining why.
Knows the expected type matters and that a bare assignment can be ambiguous.
Articulates expected-type-driven resolution, the ambiguity error, and the type-annotation fix; knows it's compile-time.
Weighs API-evolution risk: adding overloads can break references or shift resolution, and designs call sites to keep the expected type explicit.
## The problem `println` has many overloads (`println()`, `println(Int)`, `println(Any?)`, `println(Char)`, ...). When you write `::println` the compiler must decide *which one* the reference denotes. ## Resolution is driven by the expected type Kotlin uses the **expected functional type** at the use site to disambiguate: ```kotlin val p: (Int) -> Unit = ::println // selects println(Int) val q: (Char) -> Unit = ::println // selects println(Char) listOf("a", "b").forEach(::println) // selects println(Any?) for String elements ``` The call site (`forEach` parameter type `(String) -> Unit`) supplies the expectation, so the overload whose signature matches is picked. ## When it's ambiguous If there's **no expected type** and several overloads remain viable, resolution fails: ```kotlin val x = ::println // ERROR: overload resolution ambiguity ``` The compiler can't choose among `println(Int)`, `println(Any?)`, etc. ### Fixes - **Annotate the type**: `val x: (Int) -> Unit = ::println`. - **Use it where the type is implied** (as a `map`/`forEach` argument). - **Wrap in a lambda** if you need a specific call shape: `{ x: Int -> println(x) }`. ## Compile-time only Unlike virtual dispatch, overload resolution for references happens entirely at **compile time**. The selected overload is baked into the generated `KFunction`/invokedynamic; there's no runtime re-selection. ## Same rules for member & constructor references Overloaded members (`Type::m`) and overloaded constructors (`::Class`) resolve identically — the expected type narrows the candidate set. This is consistent with normal Kotlin overload resolution applied to the reference's denoted callable. ## Practical guidance When exposing references in public APIs, prefer contexts where the expected type is explicit, so future overload additions don't silently change resolution or introduce ambiguity.
- Why does `val x = ::println` fail but `list.forEach(::println)` compile?`forEach` supplies an expected functional type `(T) -> Unit` that selects one overload; the bare `val` with no annotation gives no expected type, leaving multiple overloads viable and thus ambiguous.
- Is the overload chosen at runtime?No. Reference overload resolution is fully compile-time; the chosen target is fixed in the generated code, with no runtime re-dispatch.
saying these in an interview costs you the question
- Claiming the JVM picks the overload at runtime
- Saying overloaded references always pick the first/most general overload
- Not knowing how to resolve the ambiguity (type annotation)
- Believing ambiguity is a runtime exception rather than a compile error
- Thinking member/constructor references use different resolution rules