skip to content

When invoking reflectively, how do KParameter objects work — their kind, index, isOptional, isVararg — and how do you use them to build correct call/callBy invocations?

level: seniorimportance: nice to knowfreq 25%

answer

  1. parameters: ordered List<KParameter>
  2. kind = INSTANCE | EXTENSION_RECEIVER | VALUE
  3. index drives call() order; receivers come first
  4. isOptional -> omittable in callBy; receivers never optional
  5. Receiver name is null — match by kind, not name

basics

~20 s

Each parameter of a callable is a KParameter with a kind (instance receiver, extension receiver, or value), an index, a name, a type, and flags like isOptional and isVararg. You use these to know the right order for call() or to build the map for callBy().

solid answer

~40 s

KCallable.parameters returns the ordered List<KParameter>. Each KParameter has: kind (KParameter.Kind.INSTANCE, EXTENSION_RECEIVER, or VALUE), index (position in that list, used by call()), name (null for receivers), type (a KType), isOptional (has a default — can be omitted in callBy), isVararg, and (Kotlin 1.x+) whether it's a context/regular value param. For call(), pass args in index order — receivers first because they appear at indices 0..n before value params. For callBy(), key the map by the KParameter object; omit optional ones to get defaults, but you MUST include INSTANCE/EXTENSION_RECEIVER and any non-optional value params. Looking parameters up via associateBy { it.name } is the common idiom, but receivers have null names so filter by kind for those.

code

kotlin · 13 lines
kotlin
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor

data class P(val a: String, val b: Int = 0)

fun main() {
    val ctor = P::class.primaryConstructor!!
    ctor.parameters.forEach {
        println("${it.index} ${it.name} kind=${it.kind} optional=${it.isOptional}")
    }
    val a = ctor.parameters.first { it.name == "a" }
    println(ctor.callBy(mapOf(a to "x")))   // P(a=x, b=0)
}

go deeper

for a junior

Knows a function has parameters you must supply in order.

for a middle

Reads KParameter.name/index to build call() args and basic callBy maps.

for a senior

Fully uses kind/isOptional/isVararg/type to construct correct call and callBy invocations across receivers and defaults.

for a principal

Designs robust reflective binders (deserializers/DI) on the KParameter model, handling nullability, varargs, and receiver kinds generically.

## The KParameter model `KCallable<*>.parameters: List<KParameter>` is ordered. Each `KParameter` carries: - **`kind: KParameter.Kind`** — one of `INSTANCE` (dispatch receiver of a member), `EXTENSION_RECEIVER` (the `this` of an extension), `VALUE` (a normal value parameter). - **`index: Int`** — its position in `parameters`; this is the order `call()` expects. - **`name: String?`** — the source name, or `null` for receivers. - **`type: KType`** — the declared type (nullable info via `type.isMarkedNullable`). - **`isOptional: Boolean`** — true if it has a default value (omittable in `callBy`). - **`isVararg: Boolean`** — true for a `vararg` parameter. ## Ordering for call() Receivers come first. For `class A { fun B.f(x: Int) }`, `parameters` is roughly `[INSTANCE(A), EXTENSION_RECEIVER(B), VALUE(x)]`, so `call()` is `f.call(aInstance, bReceiver, 7)`. ```kotlin import kotlin.reflect.KParameter val p = fn.parameters val instance = p.first { it.kind == KParameter.Kind.INSTANCE } val value = p.first { it.kind == KParameter.Kind.VALUE && it.name == "x" } ``` ## Building callBy() maps ```kotlin val args = buildMap<KParameter, Any?> { put(instance, aInstance) // omit value params that are isOptional to take defaults if (someValue != null) put(value, someValue) } fn.callBy(args) ``` Rules: - **Omit** a key only if `isOptional` — otherwise `IllegalArgumentException`. - Receivers (`INSTANCE`/`EXTENSION_RECEIVER`) are never optional — always include them. - For a `vararg` param, supply an array as the value. - `name` is `null` for receivers, so `associateBy { it.name }` will collide on receivers; filter by `kind` instead. ## Why it matters This model is what lets serialization and DI frameworks map JSON/field names to constructor parameters precisely, honoring defaults and nullability, without hardcoding positions.

  • Why filter receivers by kind instead of name?
    Receiver KParameters (INSTANCE, EXTENSION_RECEIVER) have name == null, so name-based lookups would miss or collide; kind reliably identifies them.
  • How do you tell from a KParameter whether you may skip it in callBy?
    Check isOptional — true means it has a default and can be omitted; false means it must be present in the map.

saying these in an interview costs you the question

  • Assuming all parameters have a non-null name
  • Treating receivers as optional in callBy
  • Ignoring isVararg when passing array arguments
  • Using index order wrongly (forgetting receivers come first)
  • Mapping a non-nullable param to null instead of omitting it

context