skip to content

Callable References

The :: operator gives you an existing function, property, or constructor as a value instead of wrapping it in a lambda. Interviewers use references to see whether you think of behavior as data.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

explore

questions

15

What is the difference between a bound reference like `instance::method` and an unbound reference like `Type::method` in Kotlin?

level: juniorimportance: must knowfreq 55%

answer

  1. Bound = receiver captured; unbound = receiver is first param
  2. Unbound arity = bound arity + 1
  3. instance::length is () -> Int; String::length is (String) -> Int
  4. Unbound fits map/filter; bound fits a fixed object callback
  5. :: is the callable-reference operator

basics

~10 s

A bound reference already remembers which object to call the method on, so you just pass arguments. An unbound reference does not; you must pass the object itself as the first argument.

solid answer

~40 s

A bound reference (`instance::method`) captures a specific receiver object at the point you create it. When you invoke it you only supply the method's own parameters. An unbound reference (`Type::method`) captures no receiver; the receiver becomes an extra leading parameter, so calling it requires passing the instance first. This shows up in the function type: for `String::length`-style member access the unbound form is `(String) -> Int`, while a bound `someString::length` would be `() -> Int`. Bound references are handy for passing an existing object's method as a callback; unbound references are ideal for `map`/`filter` over a collection of objects (`list.map(String::uppercase)`), where each element is fed in as the receiver.

code

kotlin · 5 lines
kotlin
val s = "hi"
val bound: () -> Int = s::length        // () -> Int
val unbound: (String) -> Int = String::length  // (String) -> Int
println(bound())            // 2
println(unbound("hello"))   // 5

go deeper

for a junior

Can state that bound captures the object and unbound needs it passed in; recognizes String::uppercase in map.

for a middle

Articulates the arity difference and writes the correct function types for both forms.

for a senior

Explains why the receiver promotes to the first parameter and picks the right form for callbacks vs collection pipelines.

for a principal

Relates this to overload resolution, expected-type inference, and how the compiler picks bound vs unbound when both could fit.

## What a callable reference is A *callable reference* is a value that points to a function, method, property, or constructor, written with the `::` operator. You can store it, pass it as an argument, and invoke it later. The key question for member functions is: *does the reference already know which object (the receiver) to operate on?* ## Bound reference — receiver is captured Writing `instance::method` produces a **bound** reference. The object on the left (`instance`) is captured and remembered. When you call the reference, you pass only the method's declared parameters — the receiver is already fixed. ```kotlin val name = "Kotlin" val getLength: () -> Int = name::length // bound to `name` println(getLength()) // 6, no argument needed val append: (String) -> String = name::plus // bound; plus takes 1 arg println(append("!")) // "Kotlin!" ``` `name::length` has type `() -> Int` because the receiver is already supplied. ## Unbound reference — receiver is a parameter Writing `Type::method` produces an **unbound** reference. No receiver is captured; instead the receiver is promoted to the **first parameter** of the resulting function type. ```kotlin val lengthOf: (String) -> Int = String::length // unbound println(lengthOf("Kotlin")) // 6, must pass the String val isBlank: (String) -> Boolean = String::isBlank println(isBlank(" ")) // true ``` Here `String::length` has type `(String) -> Int` — one more parameter than the bound form. ## Why arity differs by exactly one The receiver always has to come from *somewhere*. A bound reference gets it from the captured object, so it disappears from the parameter list. An unbound reference gets it from the caller, so it appears as a leading parameter. That is why the unbound form has **arity = bound arity + 1**. ## Typical usage with `map` / `filter` Unbound references shine when each collection element should become the receiver: ```kotlin listOf("a", "bb", "").filter(String::isNotEmpty) // [a, bb] listOf("a", "b").map(String::uppercase) // [A, B] ``` `filter` expects `(String) -> Boolean`, which is exactly the unbound arity. A `::isEmpty`-style member reference written as `String::isEmpty` is `(String) -> Boolean`. ## Bound to `this` Inside a class you can write `this::method` (or just `::method` for top-level functions in the same file scope), which is bound to the current instance. ## Summary table - Bound: `obj::m` → receiver captured → type drops the receiver param. - Unbound: `Type::m` → receiver is first param → type gains a leading param.

  • Which form would you pass to `list.map(...)` to uppercase every string, and why?
    The unbound `String::uppercase`, because `map` needs `(String) -> String` and the unbound reference takes each element as its receiver/first argument.
  • What is the function type of `"abc"::get`?
    `(Int) -> Char` — it is bound to the string, so only the index parameter remains.

A bound reference is a pre-addressed envelope (receiver filled in); an unbound reference is a blank envelope where you write the address (receiver) every time you send it.

saying these in an interview costs you the question

  • Saying both forms have the same arity
  • Claiming Type::method captures an instance
  • Thinking instance::method needs you to pass the instance again
  • Confusing `::` with the safe-call `?.` operator
  • Believing unbound references only work for top-level functions

context

open as a page

What does the `::` operator do when written as `list.map(::println)`, and how is it different from writing `list.map { println(it) }`?

level: juniorimportance: must knowfreq 70%

basics

~20 s

::println turns an existing function into a value you can pass around. It does the same job as the lambda { println(it) }, just shorter, because you reuse a function that already exists instead of writing a new one.

open as a page

What is a property reference like ::name or Person::name in Kotlin, and what can you do with the value you get?

level: juniorimportance: must knowfreq 55%

basics

~10 s

It is a value that points to a property instead of reading it. You can pass it to functions like map to read that property from many objects, for example people.map(Person::name).

open as a page

Given `class Box(val n: Int) { fun scaled(f: Int) = n * f }`, what are the function types of `Box::scaled` and `box::scaled`, and how do you invoke each?

level: middleimportance: must knowfreq 45%

basics

~10 s

Box::scaled needs both the box and the factor, so its type is (Box, Int) -> Int. box::scaled already has the box, so it only needs the factor: (Int) -> Int.

open as a page

How does a constructor reference (::ClassName) work, and how would you use it as a factory passed to map?

level: middleimportance: must knowfreq 50%

basics

~20 s

::ClassName is a value that creates new objects of that class. Its parameters match the constructor, so you can pass it where a function is expected, like ids.map(::User) to turn each id into a User.

open as a page

Why does `list.filter(String::isEmpty)` work for a `List<String>`, and how does the unbound `::isEmpty`-style reference satisfy `filter`'s expected `(String) -> Boolean` type?

level: middleimportance: should knowfreq 38%

basics

~10 s

String::isEmpty is a function that takes one string and returns true/false. filter calls it once per element, passing each string in. That matches exactly what filter wants.

open as a page

You write `val f = ::max` but `max` has several overloads and it won't compile. How does Kotlin resolve which function a `::name` reference points to, and how do you disambiguate?

level: middleimportance: should knowfreq 45%

basics

~20 s

A ::name reference must match exactly one function. When the name has several overloads, Kotlin needs the expected type to choose. Give it one by declaring the variable's function type, and it picks the matching overload.

open as a page

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%

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.

open as a page

Using a property reference's reflection surface, how do you read a name, read a value, and mutate a property — and what types make set available?

level: middleimportance: should knowfreq 40%

basics

~10 s

On a property reference you can call .name to get the property's name, .get(obj) to read its value, and .set(obj, value) to change it. Only references to var properties (mutable ones) allow .set.

open as a page

Show how property references combine with collection operators (sortedBy, groupBy, associateBy, maxByOrNull) and explain what happens at the type level.

level: middleimportance: should knowfreq 38%

basics

~10 s

Many collection functions take a 'selector' function that returns a value per item. A property reference like Person::age fits perfectly: people.sortedBy(Person::age) sorts by age, people.groupBy(Person::city) groups by city.

open as a page

When is the receiver of a bound reference captured, and how do `this::method` and bound references to a `var` behave over time?

level: seniorimportance: should knowfreq 28%

basics

~10 s

The object is captured the moment you write the bound reference, not when you call it. If you later reassign the variable, the reference still points at the original object.

open as a page

Is `xs.map(::process)` ALWAYS substitutable for `xs.map { process(it) }`? Give concrete cases where a function reference cannot replace the lambda, or behaves differently.

level: seniorimportance: should knowfreq 35%

basics

~20 s

No. A reference only works when you forward the argument straight through, unchanged. If you need to transform the input, use defaults, reorder or drop parameters, add captured values, or handle overloads the compiler can't pick, you must use a lambda.

open as a page

When should you prefer Type::prop or ::ClassName over an equivalent lambda, and what are the trade-offs and pitfalls?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Use references when the lambda would just call one property or constructor (Person::name instead of { it.name }). They are shorter and clearer. Use a lambda when you need extra logic, defaults, or to control which overload is called.

open as a page

When both a bound and an unbound interpretation of a `::method` reference could satisfy the expected type, how does Kotlin resolve it, and where can ambiguity or surprises arise?

level: principalimportance: nice to knowfreq 14%

basics

~20 s

Kotlin uses the expected function type to pick the right meaning. Whether you wrote a type name or an instance, plus how many parameters the target slot has, decides bound vs unbound; sometimes you must spell it out to remove ambiguity.

open as a page

On a code review you see a pipeline rewritten from lambdas to function references (`.map(::normalize).filter(::isValid).map(::toDto)`). As a senior/principal, when do you endorse this style and when do you push back?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Endorse references when each step just forwards the element to one well-named function — it reads clearly and reuses logic. Push back when it forces awkward helper functions, hides parameters, or breaks if someone later needs to tweak an argument.

open as a page