skip to content

What is a callable reference in Kotlin, and how do you write a reference to a top-level function, a member function, and a property?

level: juniorimportance: must knowfreq 70%

answer

  1. :: = "point at", don't call
  2. ::fn top-level, Class::m unbound, obj::m bound
  3. String::length has type (String) -> Int
  4. Result is KFunction/KProperty (a KCallable)
  5. Replaces trivial lambdas in map/filter

basics

~10 s

A callable reference points at an existing function or property by name using the :: operator, so you can pass it around instead of writing a lambda. Example: list.map(String::length).

solid answer

~30 s

A callable reference uses the :: operator to grab an existing declaration as a value instead of calling it. ::topLevelFun references a top-level function; String::length references a member (here a property) on the class; obj::method binds to a specific instance. The result is a function-typed object (KFunction/KProperty) you can pass where a lambda is expected, e.g. names.map(String::uppercase) or list.filter(::isValid). It avoids the boilerplate lambda { it.uppercase() } and reads more declaratively. The reference's type matches the underlying signature, so String::length has type (String) -> Int. References also implement KCallable, so they double as reflection handles.

code

kotlin · 6 lines
kotlin
fun shout(s: String) = s.uppercase() + "!"
val words = listOf("hi", "yo")

val a = words.map(::shout)        // top-level fun ref
val b = words.map(String::uppercase) // unbound member ref, (String)->String
val c: () -> Int = "hello"::length   // bound member ref

go deeper

for a junior

Knows :: makes a reference, can use String::length in map and pass ::topLevelFun where a lambda is expected.

for a middle

Distinguishes unbound vs bound and states the resulting function type precisely.

for a senior

Connects references to KFunction/KProperty/KCallable and knows the stdlib-vs-kotlin-reflect line for plain use vs introspection.

for a principal

Frames references as the bridge between Kotlin's functional-value model and its reflection model and reasons about API design implications of accepting references.

## What a callable reference is A **callable reference** turns an existing function or property into a **value** you can pass around, instead of *calling* it. You create one with the **`::` operator**. Think of it as "point at this declaration" rather than "invoke this declaration". ## The three common forms ```kotlin // 1. Top-level (or local) function reference — nothing before :: fun isOdd(x: Int) = x % 2 == 1 val refs = listOf(1, 2, 3).filter(::isOdd) // ::isOdd has type (Int) -> Boolean // 2. Member reference on a CLASS — ClassName::member (unbound) val lengths = listOf("a", "bb").map(String::length) // (String) -> Int // 3. Member reference on an INSTANCE — instance::member (bound) val s = "hello" val getLen: () -> Int = s::length // already bound to s, no String param ``` ## Why use them - **Less boilerplate** than a lambda: `String::uppercase` vs `{ s -> s.uppercase() }`. - **Declarative**: the name documents intent. - They satisfy any **function type** parameter (`(T) -> R`), so they slot into `map`, `filter`, `let`, etc. ## What you actually get back The `::` expression produces an object implementing **`KFunction`** (for functions) or **`KProperty`** (for properties), both of which extend **`KCallable`**. So a reference is simultaneously: - usable as a **function** (you can `invoke()` / call it), and - a **reflection handle** (it has `.name`, `.parameters`, etc.). Basic function-as-value usage (calling it) works with only the standard library. Deeper reflection on it (reading `.name`, `.parameters`) needs the **`kotlin-reflect`** artifact at runtime. ## Quick mental model `obj.method()` = call now. `obj::method` = a thing I can call later.

  • What type does String::length have?
    (String) -> Int — it's an unbound reference, so the receiver becomes the first parameter.
  • Do you need kotlin-reflect just to pass String::uppercase to map?
    No. Using a reference as a plain function value works with the stdlib alone; kotlin-reflect is only needed to introspect it (read name, parameters, etc.).

A lambda is writing a sticky note 'go do X'; a callable reference is just handing someone the phone number of the function that already does X.

saying these in an interview costs you the question

  • Confusing :: with calling — saying obj::method() runs the method now
  • Thinking ::fn returns the function's return value instead of a function object
  • Believing every callable reference always requires kotlin-reflect
  • Not knowing the receiver becomes a parameter in an unbound reference

context