skip to content

How does type inference work for factory functions like listOf, and what surprises arise with mixed types or spreading an array?

level: seniorimportance: nice to knowfreq 35%

answer

  1. listOf infers least common supertype
  2. mixed types -> List<Any>
  3. null arg -> List<T?>; listOfNotNull -> List<T>
  4. spread array into vararg with *
  5. empty: prefer emptyList<T>()

basics

~10 s

Kotlin guesses the element type from the arguments you pass. Mixing types gives a common supertype like Any. You can pass an existing array into the vararg using the spread operator *.

solid answer

~40 s

Factory functions are generic with vararg, so listOf(1, 2) infers List<Int>, while listOf(1, "x") infers the least common supertype, here List<Any> (or List<Comparable<*>> depending on members). You can pin the type with an explicit argument: listOf<Number>(1, 2L). Calling listOf() with no args needs an expected type and otherwise infers List<Nothing>/needs help — emptyList() is the clearer choice. Because the parameter is vararg, you can spread an existing array with the * operator: listOf(*array). For maps, the element type is the Pair type, so mixed value types widen V. Watch nullability: listOf(1, null) is List<Int?>, whereas listOfNotNull(1, null) is List<Int>. Understanding inference avoids accidental List<Any> or unexpected nullable element types.

code

kotlin · 6 lines
kotlin
val mixed = listOf(1, "x")        // List<Any>
val nums = listOf<Number>(1, 2L)  // pinned
val withNull = listOf(1, null)    // List<Int?>
val clean = listOfNotNull(1, null)// List<Int>
val arr = arrayOf(1, 2, 3)
val spread = listOf(0, *arr, 4)   // [0, 1, 2, 3, 4]

go deeper

for a junior

Knows Kotlin infers the element type and you can write listOf(1, 2).

for a middle

Knows mixed types give Any and you can specify the type explicitly.

for a senior

Explains least-common-supertype inference, null widening to T?, the spread operator, and contrasts listOf vs listOfNotNull typing.

for a principal

Anticipates inference-driven API design issues (accidental List<Any> at boundaries) and guides when to pin types or sanitize inputs.

## Generic + vararg = inferred element type `listOf` is `fun <T> listOf(vararg elements: T): List<T>`. The compiler infers `T` from the arguments by computing their **least common supertype**. ```kotlin val a = listOf(1, 2, 3) // T = Int -> List<Int> val b = listOf(1, 2L) // T = Number? widening -> List<Number> (or Comparable<*>) val c = listOf(1, "x") // T = Any -> List<Any> ``` When arguments share no useful supertype other than `Any`, you get `List<Any>` — often a sign you did not mean to mix types. ## Pinning the type explicitly Provide the type argument to control `T`: ```kotlin val n = listOf<Number>(1, 2L, 3.0) // List<Number> ``` ## Nullability flows through inference A `null` argument widens the element type to nullable: ```kotlin val xs = listOf(1, null) // List<Int?> val ys = listOfNotNull(1, null) // List<Int> — nulls stripped, non-null T ``` This is a frequent gotcha: `listOf` keeps nulls and makes `T?`; `listOfNotNull` removes them and keeps `T`. ## Empty call needs context `listOf()` with no arguments cannot infer `T` from arguments; it relies on an expected type, otherwise you should use `emptyList<T>()`: ```kotlin val e: List<String> = listOf() // OK, T from declared type val f = emptyList<String>() // clearer ``` ## Spreading an array into vararg Because the parameter is `vararg`, you can pass an existing array using the **spread operator `*`**: ```kotlin val arr = arrayOf(1, 2, 3) val list = listOf(*arr) // spreads elements into the vararg val combined = listOf(0, *arr, 4) ``` Note `*` works with an `Array<T>` (and the primitive array types). Spreading is a copy of references into the vararg array. ## Maps and value widening For `mapOf`, `K` and `V` are inferred from the `Pair`s, so mixed value types widen `V`: ```kotlin val m = mapOf("a" to 1, "b" to "x") // Map<String, Any> ``` ## Why it matters Unintended `List<Any>` or `List<T?>` results break downstream type-safe operations. Reading inference rules lets you either embrace the widening or pin the type/clean the input.

  • Why does listOf(1, "x") compile, and what type is it?
    Kotlin infers the least common supertype of Int and String, which is Any, giving List<Any>. It compiles but is rarely what you want.
  • How do you pass an existing array to listOf?
    Use the spread operator: listOf(*array). It expands the array elements into the vararg parameter.
  • How do listOf and listOfNotNull differ in element nullability?
    listOf(x, null) yields List<T?> keeping the null; listOfNotNull(x, null) yields List<T> with nulls removed.

Type inference is like a tailor sizing a coat to fit everyone in the room: the more different the people (types), the looser (more general, Any) the coat must be.

saying these in an interview costs you the question

  • Not knowing mixed types widen to Any
  • Unaware that null widens element type to T?
  • Trying to pass an array without the * spread operator
  • Assuming listOf() with no args always works without context
  • Confusing listOf nullability behavior with listOfNotNull

context