How does Kotlin resolve which function an overloaded callable reference like ::println points to, and how do you disambiguate?
answer
- Overloaded reference resolved by expected type
- No expected type -> ambiguity error
- Disambiguate with explicit (T)->R type
- Or wrap in a typed lambda
- ::Ctor follows constructor-overload rules
basics
~20 sWhen a name is overloaded, the compiler picks the overload by looking at the expected function type from the context. If that isn't enough, you add an explicit type to tell it which one you mean.
solid answer
~40 sAn overloaded reference such as ::println is ambiguous on its own. Kotlin resolves it using the expected type from context: if the parameter or variable is typed (String) -> Unit, it selects the println(String) overload. When there's no expected type — e.g., assigning to a var without a declared type, or the context fits several overloads — the compiler reports an ambiguity. You disambiguate by supplying an explicit function type: val p: (Int) -> Unit = ::println, or by using a wrapping lambda { x: Int -> println(x) } that pins the argument types. The same applies to references to your own overloaded methods (Foo::bar). Constructor references ::Foo follow identical rules across constructor overloads. Note generic functions may also need explicit type arguments via the reference target.
code
kotlin · 8 linesfun handle(x: Int) = println("int $x")
fun handle(x: String) = println("str $x")
// val bad = ::handle // error: ambiguous, no expected type
val forInts: (Int) -> Unit = ::handle // resolves to handle(Int)
val forStrs: (String) -> Unit = ::handle // resolves to handle(String)
listOf(1, 2, 3).forEach(::handle) // expected (Int)->Unit -> handle(Int)go deeper
May not realize references can be ambiguous; can copy a working example.
Knows overloaded references can be ambiguous and that an explicit type or lambda fixes it.
Explains type-directed resolution via expected type, predicts when ambiguity arises, and extends it to constructor and generic references.
Anticipates API ergonomics — designs overload sets so common reference use sites stay unambiguous and documents the expected types.
## The problem Kotlin allows **overloaded** functions (same name, different parameters). A reference is just a name plus `::`, so `::println` could mean any of the many `println` overloads (`println(Int)`, `println(String)`, `println(Any?)`, `println()`, ...). The compiler must choose one **concrete** target. ## How resolution works: expected type drives it Reference resolution is **type-directed**. The compiler looks at the **expected function type** at the use site and picks the overload whose signature matches: ```kotlin val p: (String) -> Unit = ::println // picks println(String) listOf(1, 2).forEach(::println) // forEach expects (Int) -> Unit -> println(Int) ``` Here `forEach` on `List<Int>` expects `(Int) -> Unit`, so the `println(Any?)`/`println(Int)` overload that fits is chosen automatically. ## When it fails If there is **no expected type**, or several overloads fit equally, you get an **ambiguity / overload resolution error**: ```kotlin val x = ::println // error: cannot choose among overloads (no expected type) ``` ## How to disambiguate 1. **Declare the expected function type** explicitly: ```kotlin val p: (Int) -> Unit = ::println ``` 2. **Wrap in a lambda** that pins the parameter types (often clearest): ```kotlin val p = { v: Int -> println(v) } ``` 3. For **your own** overloaded members, the same applies — give the variable/parameter a precise function type. ## Related cases - **Constructor references** `::ClassName` resolve across **constructor overloads** by the same expected-type mechanism (e.g., `val f: (String) -> User = ::User`). - **Generic functions**: a reference may need the surrounding context to infer type arguments; sometimes you specify them on the variable's function type. - **Bound vs unbound** doesn't change the rule, but it changes the expected type (the receiver slot), which can itself break a tie. ## Mental model Think of a callable reference as an **expression whose type the compiler must infer**; with overloads, the expected type is the tie-breaker. Give it one and ambiguity disappears.
- Why does listOf(1,2).forEach(::println) compile but val x = ::println does not?forEach supplies an expected type (Int) -> Unit that selects an overload; the bare assignment has no expected type, so the overload set stays ambiguous.
- Do constructor references obey the same disambiguation rules?Yes — ::Foo across overloaded constructors is resolved by the expected function type at the use site.
saying these in an interview costs you the question
- Claiming ::println always picks one fixed overload regardless of context
- Not knowing the expected type drives resolution
- Suggesting reflection to disambiguate instead of an explicit function type
- Thinking ambiguity is a runtime error rather than a compile error