How do you create a `::name` reference to a top-level function versus a member function of a class, and what does each reference's type look like?
answer
- Top-level = `::name`, signature unchanged
- Member = `Class::method`, receiver becomes param 0
- `String::length` -> `(String) -> Int`
- Unbound = instance supplied at call time
- `map(String::length)` uses element as receiver
basics
~20 sA top-level function is just ::name. For a member function you qualify it with the class, like String::length. The class-qualified one needs an instance to run, so its type includes the receiver as a first parameter.
solid answer
~40 sFor a **top-level** function `fun parse(s: String): Int`, the reference is `::parse` with type `(String) -> Int`. For a **member** (or extension) function, you can write a **class-qualified unbound reference** `String::length`, whose type prepends the receiver: `(String) -> Int` — the instance becomes the first parameter. This is why `list.map(String::length)` works: each `String` element is passed as the receiver. A top-level function's reference carries no implicit receiver, so its parameter list is exactly the declared parameters. The compiler still uses the expected type to disambiguate overloads in both cases. Class-qualified references are ideal in collection pipelines where the element is the receiver. (Binding a member to a specific instance — `myStr::length` — is a separate, related mechanism covered under bound references.)
code
kotlin · 12 lines// Top-level
fun double(x: Int) = x * 2
val t: (Int) -> Int = ::double
// Member (class-qualified, unbound)
class Box(val size: Int) { fun grow(by: Int) = Box(size + by) }
val g: (Box, Int) -> Box = Box::grow // receiver Box is first param
// Pipeline use: element becomes the receiver
val names = listOf("al", "bob")
println(names.map(String::uppercase)) // [AL, BOB]
println(g(Box(1), 4).size) // 5go deeper
Knows top-level is ::name and member needs Class::method.
Correctly states the receiver-as-first-parameter type for unbound member references and uses it in pipelines.
Explains why unbound references defer receiver selection and how extension functions follow the same rule.
Connects this to API ergonomics — designing element-as-receiver methods so Class::method reads cleanly in pipelines.
## Top-level function references A **top-level function** is declared at file scope, outside any class. Its reference is simply `::name`, and its type is exactly its declared signature as a function type. ```kotlin fun parse(s: String): Int = s.toInt() val f: (String) -> Int = ::parse // type: (String) -> Int println(listOf("1", "2").map(::parse)) // [1, 2] ``` There is no receiver, so the parameter list of the function type matches the declared parameters one-for-one. ## Member / extension function references (class-qualified, unbound) A **member function** belongs to a class and needs an instance (the **receiver**) to run. You reference it by qualifying with the class: `ClassName::member`. This is an **unbound reference** — it isn't tied to any particular object yet, so the receiver shows up as the **first parameter** of the function type. ```kotlin // String.length is a member-like property; methods behave the same: val lengths: (String) -> Int = String::length // receiver String becomes param 0 fun greet(name: String) = "Hi $name" // top-level class Greeter { fun greet(name: String) = "Hi $name" } val m: (Greeter, String) -> String = Greeter::greet // receiver + declared param ``` In a pipeline this lets each element act as the receiver: ```kotlin listOf("a", "bb", "ccc").map(String::length) // [1, 2, 3] // each String element supplied as the receiver of length ``` ## Why the receiver becomes a parameter Calling a member needs an object. An **unbound** reference defers choosing that object, so it must be provided at call time — hence the receiver sits at the front of the parameter list. The function type for `Class::method(args)` is `(Class, args...) -> ReturnType`. ## Summary table - Top-level `fun f(a: A): R` -> `::f` -> `(A) -> R`. - Member `class C { fun m(a: A): R }` -> `C::m` -> `(C, A) -> R`. - Extension `fun A.ext(b: B): R` -> `A::ext` -> `(A, B) -> R`. ## Overloads apply equally Both forms still rely on the **expected type** to pick among overloads, exactly as for any `::name` reference. ```kotlin fun describe(n: Int) = "n=$n" fun describe(s: String) = "s=$s" val d: (Int) -> String = ::describe // picks the Int overload ```
- What's the type of `Greeter::greet` if `greet(name: String): String`?`(Greeter, String) -> String` — the receiver `Greeter` is prepended as the first parameter because the reference is unbound.
- Does `map(String::length)` create a new object per element?No. The reference object is created once; `map` calls its `invoke` with each element as the receiver argument.
saying these in an interview costs you the question
- Saying a member reference has the same type as the top-level signature
- Forgetting the receiver becomes the first parameter for unbound member references
- Thinking `Class::method` is already bound to an instance
- Claiming you can't reference extension functions
- Confusing `String::length` with `someString::length`