skip to content

A callable reference is both a function value and a reflection handle. What reflective information can you read off it, and what runtime dependency does that require?

level: seniorimportance: should knowfreq 45%

answer

  1. Reference = function value + KCallable handle
  2. Read name/parameters/returnType/visibility
  3. callBy(map) = named + default args
  4. Plain use: stdlib; introspection: kotlin-reflect
  5. Missing artifact -> KotlinReflectionNotSupportedError

basics

~10 s

A reference object also exposes metadata like its name and parameters because it's a KCallable. Reading that metadata at runtime needs the kotlin-reflect library on the classpath; just calling the reference does not.

solid answer

~40 s

Because `::` yields a KFunction/KProperty (both KCallable), you can introspect it: read `.name`, inspect `.parameters` (each a KParameter with name/type/kind), check `.returnType` (a KType), read `.visibility`, `.isAbstract`, and the function modifiers `isInline`, `isInfix`, `isOperator`, `isSuspend`. For properties you also get `.getter` and, for KMutableProperty, `.setter`, plus `.get(receiver)` / `.set(...)`. You can also `callBy(map)` to invoke with named/default arguments. Crucially, two tiers exist: using a reference purely as a function value (passing it to map, calling invoke) works with only kotlin-stdlib. The moment you touch the reflective surface — `.name`, `.parameters`, `.returnType`, `.callBy` — the JVM needs the `org.jetbrains.kotlin:kotlin-reflect` artifact at runtime, or you get KotlinReflectionNotSupportedError. That artifact is sizable (a few MB), so teams weigh it on Android/serverless.

code

kotlin · 9 lines
kotlin
import kotlin.reflect.full.memberProperties

data class User(val id: Long, val email: String)

fun columnNames(): List<String> =
    User::class.memberProperties.map { it.name }   // ["id", "email"] — needs kotlin-reflect

val emailRef = User::email
val value = emailRef.get(User(1, "[email protected]"))       // reflective getter call

go deeper

for a junior

Knows a reference has a .name and that some reflection exists, even if vague on the dependency.

for a middle

Lists several KCallable members and knows kotlin-reflect is needed for reflection but not for plain calls.

for a senior

Articulates the two-tier dependency boundary precisely, names the error, and reasons about kotlin-reflect's size cost on Android/serverless.

for a principal

Weighs library API design (accepting KProperty pulls in the artifact transitively) and proposes alternatives when the dependency is unacceptable.

## Two hats, one object Every `::` expression returns an object that is **both**: 1. a **function value** (it implements a function type, so `map`/`invoke` work), and 2. a **`KCallable`** reflection handle (`KFunction` for functions, `KProperty`/`KMutableProperty` for properties). ## What you can read off it From **`KCallable`** (shared): - `name`: declared name. - `parameters`: `List<KParameter>` — each has `name`, `type` (a `KType`), `kind` (`INSTANCE`, `EXTENSION_RECEIVER`, `VALUE`), `isOptional`, `isVararg`. - `returnType`: a `KType` (with nullability + type args). - `visibility`, `isAbstract`, `isFinal`, `isOpen`, `typeParameters`, `annotations`. - `call(vararg args)` and `callBy(Map<KParameter, Any?>)` to invoke with positional or named/default args. Extra on **`KFunction`**: `isInline`, `isInfix`, `isOperator`, `isSuspend`, `isExternal`. Extra on **`KProperty`**: `getter` (itself a `KFunction`); on **`KMutableProperty`**: `setter`, plus `get(...)`/`set(...)`. ```kotlin import kotlin.reflect.full.* fun area(w: Int, h: Int = 1) = w * h val f = ::area println(f.name) // "area" println(f.parameters.map { it.name }) // [w, h] println(f.returnType) // kotlin.Int println(f.parameters[1].isOptional) // true (h has a default) // invoke with named args / defaults val byName = f.callBy(mapOf(f.parameters[0] to 5)) // h defaults to 1 -> 5 ``` ## The dependency boundary — the senior point There are **two tiers**: - **Plain functional use** — `list.map(String::length)`, `ref.invoke(x)`, storing it in a `(String)->Int` — needs **only kotlin-stdlib**. The compiler synthesizes a lightweight function object. - **Reflective use** — touching `.name`, `.parameters`, `.returnType`, `.annotations`, `.callBy`, `.getter` — requires **`org.jetbrains.kotlin:kotlin-reflect`** on the runtime classpath. Without kotlin-reflect, those reflective members throw **`KotlinReflectionNotSupportedError`** (a small stub backs the function-value path; the full implementation lives in kotlin-reflect). `kotlin-reflect` is a few MB, which matters for **Android dex/method-count** and **serverless cold start**, so adding it is a deliberate choice. ## Practical implication If a library accepts a `KProperty` to read a name (common in validation/serialization DSLs), it pulls in kotlin-reflect transitively. Audit that before assuming references are free.

  • What error do you get if you read .parameters without kotlin-reflect on the classpath?
    KotlinReflectionNotSupportedError — the full reflection implementation isn't present; only the lightweight function-value path is.
  • What's the difference between call and callBy on a KCallable?
    call takes positional arguments in declaration order; callBy takes a Map<KParameter, Any?>, letting you skip optional/default parameters and supply by name.

saying these in an interview costs you the question

  • Claiming passing String::length to map requires kotlin-reflect
  • Saying all reflection is free in Kotlin
  • Not knowing the KotlinReflectionNotSupportedError failure mode
  • Confusing call (positional) with callBy (named/defaults)
  • Ignoring kotlin-reflect's size cost on Android/serverless

context