skip to content

How do you inspect a KFunction's parameters, returnType, and visibility, and what do the KParameter.kind values mean?

level: seniorimportance: should knowfreq 30%

answer

  1. parameters includes receivers (kind = INSTANCE/EXTENSION_RECEIVER/VALUE)
  2. receiver params have name == null
  3. isOptional = has a default value
  4. returnType is a KType (nullability + arguments)
  5. callBy honors defaults; call does not

basics

~20 s

Read function.parameters to get a list of KParameter (each has name, type, index, and a kind), function.returnType for the result type, and function.visibility for public/internal/etc. The kind tells you if a parameter is the receiver or a normal value.

solid answer

~30 s

On any `KCallable`, `parameters: List<KParameter>` lists formal parameters in declaration order, **including receivers**. Each `KParameter` has `name: String?` (null for receivers), `type: KType`, `index: Int`, `isOptional` (has a default), `isVararg`, and `kind: KParameter.Kind` — one of `INSTANCE` (dispatch receiver, the object the member belongs to), `EXTENSION_RECEIVER` (the extension's receiver), or `VALUE` (an ordinary argument). `returnType: KType` describes the result, with nullability and type arguments accessible via `KType.isMarkedNullable` and `arguments`. `visibility: KVisibility?` is `PUBLIC/PROTECTED/INTERNAL/PRIVATE` or `null`. To invoke by name with defaults, build a `Map<KParameter, Any?>` and call `callBy`, which honors `isOptional` defaults — unlike `call`, which requires every value parameter.

code

kotlin · 7 lines
kotlin
import kotlin.reflect.full.functions
import kotlin.reflect.full.valueParameters

class Calc { fun add(a: Int, b: Int = 0) = a + b }
val add = Calc::class.functions.first { it.name == "add" }
println(add.valueParameters.map { it.name to it.isOptional }) // [(a, false), (b, true)]
println(add.returnType) // kotlin.Int

go deeper

for a junior

Knows parameters, returnType, and visibility exist and can read a parameter's name and type.

for a middle

Distinguishes the three KParameter.Kind values and knows receivers appear in parameters with null names.

for a senior

Uses callBy with a KParameter map to honor defaults and leverages instanceParameter/valueParameters helpers.

for a principal

Designs robust reflective invokers handling varargs, optionals, receivers, and nullability across many signatures.

## Inspecting a function Given a `KFunction` (from `::fn`, `declaredFunctions`, etc.), three properties carry most metadata: - **`parameters: List<KParameter>`** — every formal parameter, **in order, including receivers**. - **`returnType: KType`** — the declared return type. - **`visibility: KVisibility?`** — `PUBLIC`, `PROTECTED`, `INTERNAL`, `PRIVATE`, or `null` when not representable. ## `KParameter` anatomy Each `KParameter` exposes: - `name: String?` — parameter name, or **`null`** for receivers. - `type: KType` — its type. - `index: Int` — position in `parameters`. - `isOptional: Boolean` — true if it has a **default value**. - `isVararg: Boolean` — true for `vararg` params. - `kind: KParameter.Kind` — the role of the parameter. ### `KParameter.Kind` values - **`INSTANCE`** — the **dispatch receiver**: the object on which a member is called (the `this` of the class). Present for unbound member references. - **`EXTENSION_RECEIVER`** — the receiver of an **extension** function/property (the type before the dot). - **`VALUE`** — an ordinary, named value argument. This is why `parameters.size` can exceed the count you wrote in the signature: receivers are counted too. ```kotlin import kotlin.reflect.KParameter import kotlin.reflect.full.declaredFunctions class Greeter { fun greet(name: String, loud: Boolean = false): String = if (loud) "HI $name" else "hi $name" } val fn = Greeter::class.declaredFunctions.first { it.name == "greet" } fn.parameters.forEach { p -> println("#${p.index} ${p.name} : ${p.type} kind=${p.kind} optional=${p.isOptional}") } // #0 null : Greeter kind=INSTANCE optional=false // #1 name : kotlin.String kind=VALUE optional=false // #2 loud : kotlin.Boolean kind=VALUE optional=true println(fn.returnType) // kotlin.String println(fn.visibility) // PUBLIC ``` ## Calling with defaults: `callBy` `call(vararg args)` demands a value for **every** value parameter (and the receiver). To use **default values**, use `callBy(Map<KParameter, Any?>)` and simply omit optional parameters: ```kotlin import kotlin.reflect.full.instanceParameter import kotlin.reflect.full.valueParameters val g = Greeter() val instanceP = fn.instanceParameter!! val nameP = fn.valueParameters.first { it.name == "name" } val result = fn.callBy(mapOf(instanceP to g, nameP to "Ada")) // 'loud' defaulted // result == "hi Ada" ``` Helpers `instanceParameter`, `extensionReceiverParameter`, and `valueParameters` (all in `kotlin.reflect.full`) filter `parameters` by kind for convenience.

  • Why does parameters.size differ from the number of arguments in the source signature?
    Because receivers (INSTANCE / EXTENSION_RECEIVER) are included as KParameters alongside the VALUE parameters.
  • When must you use callBy instead of call?
    When you want to rely on default values for some parameters; call requires every value parameter to be supplied.
  • What is the name of a receiver KParameter?
    It is null; receivers have no parameter name.

saying these in an interview costs you the question

  • Assuming parameters excludes the receiver
  • Using call() and being surprised defaults aren't applied
  • Thinking every KParameter has a non-null name
  • Confusing isOptional (has default) with isVararg or nullability

context