skip to content

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?

level: middleimportance: should knowfreq 40%

answer

  1. Top-level = `::name`, signature unchanged
  2. Member = `Class::method`, receiver becomes param 0
  3. `String::length` -> `(String) -> Int`
  4. Unbound = instance supplied at call time
  5. `map(String::length)` uses element as receiver

basics

~20 s

A 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 s

For 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
kotlin
// 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)             // 5

go deeper

for a junior

Knows top-level is ::name and member needs Class::method.

for a middle

Correctly states the receiver-as-first-parameter type for unbound member references and uses it in pipelines.

for a senior

Explains why unbound references defer receiver selection and how extension functions follow the same rule.

for a principal

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`

context