skip to content

How do isMarkedNullable and the arguments list work on a KType, and how do you inspect a nested generic type?

level: middleimportance: should knowfreq 38%

answer

  1. isMarkedNullable = trailing ? on the outer type only
  2. arguments: List<KTypeProjection>; each has type and variance
  3. Star projection => projection.type is null
  4. Recurse via argument.type to inspect nested generics
  5. List<String?> != List<String>? as KTypes

basics

~20 s

isMarkedNullable is true when the type was written with a question mark, like String?. The arguments list holds the type arguments; each one has a .type you can read, and you can recurse into it for nested generics.

solid answer

~40 s

A KType exposes nullability via isMarkedNullable (true exactly when a trailing ? was written, e.g. List<String>?). Its arguments is a List<KTypeProjection>; each KTypeProjection has a type: KType? and a variance: KVariance?. The type is null when the projection is a star (*). To inspect a nested generic like Map<String, List<Int>?>, you walk arguments: arguments[0].type is String, arguments[1].type is List<Int>?, and recursing into that gives Int. Note isMarkedNullable reflects the source mark, not deep nullability of arguments — Map<String, List<Int>?> is not itself nullable even though an argument is. Build these with typeOf for safety. Variance values are KVariance.IN, OUT, INVARIANT; a projection with a null type plus the star is how Array<*> appears.

code

kotlin · 17 lines
kotlin
import kotlin.reflect.KType
import kotlin.reflect.typeOf

fun render(t: KType): String {
    val name = (t.classifier as? kotlin.reflect.KClass<*>)?.simpleName ?: "?"
    val args = t.arguments.joinToString(", ") { proj ->
        proj.type?.let { render(it) } ?: "*"
    }
    val generics = if (args.isEmpty()) "" else "<$args>"
    val nullMark = if (t.isMarkedNullable) "?" else ""
    return "$name$generics$nullMark"
}

fun main() {
    println(render(typeOf<Map<String, List<Int>?>>()))
    // Map<String, List<Int>?>
}

go deeper

for a junior

Knows isMarkedNullable maps to a trailing ? and arguments holds the type arguments.

for a middle

Can walk arguments via KTypeProjection.type and explain star-projection nulls and shallow nullability.

for a senior

Writes correct recursive descent handling variance and star projections without NPEs.

for a principal

Reasons about how a framework should normalize/compare nested KTypes (e.g. for a serialization registry) including variance edge cases.

## isMarkedNullable `KType.isMarkedNullable: Boolean` is `true` if and only if the type was written with a trailing `?`. It describes the *outer* type only. ```kotlin import kotlin.reflect.typeOf println(typeOf<String>().isMarkedNullable) // false println(typeOf<String?>().isMarkedNullable) // true println(typeOf<List<Int?>>().isMarkedNullable) // false — the List itself isn't nullable ``` ## arguments and KTypeProjection `KType.arguments: List<KTypeProjection>`. Each `KTypeProjection` has: - **type: KType?** — the argument's type, or `null` for a star projection (`*`). - **variance: KVariance?** — `KVariance.INVARIANT`, `IN`, or `OUT` (`null` together with a `null` type for a star). ```kotlin import kotlin.reflect.typeOf val t = typeOf<Map<String, List<Int>?>>() val first = t.arguments[0].type // kotlin.String val second = t.arguments[1].type // kotlin.collections.List<kotlin.Int>? val inner = second!!.arguments[0].type // kotlin.Int println(second.isMarkedNullable) // true — List<Int>? argument is nullable ``` ## Walking nested generics Because each argument's `type` is itself a `KType`, you recurse to inspect arbitrarily deep generics — a common need in serialization and validation frameworks. A clean recursive descent reads `type.classifier` plus `type.arguments` at each level. ## Variance and star projections For `MutableList<out Number>` the single projection has `variance = KVariance.OUT`. For `List<*>` the projection has `type == null` and represents a star projection (closely related to `starProjectedType`, covered separately). Always null-check `KTypeProjection.type` before recursing, since star projections carry no concrete type. ## Gotcha `isMarkedNullable` is shallow: `List<String>?` is nullable; `List<String?>` is not (the element is). Don't conflate the two — they are different `KType`s with `==` returning false.

  • When is a KTypeProjection's type null?
    For a star projection (*), e.g. in List<*>; the projection then carries no concrete type, so you must null-check before recursing.
  • Does isMarkedNullable on List<String?> return true?
    No. The List itself is not nullable; only its element type is. isMarkedNullable describes the outer type only.

saying these in an interview costs you the question

  • Treating List<String?> and List<String>? as the same KType
  • Forgetting that projection.type can be null for star projections
  • Assuming isMarkedNullable reflects nullability of arguments
  • Calling arguments a List<KType> instead of List<KTypeProjection>

context