How does type inference work for factory functions like listOf, and what surprises arise with mixed types or spreading an array?
answer
- listOf infers least common supertype
- mixed types -> List<Any>
- null arg -> List<T?>; listOfNotNull -> List<T>
- spread array into vararg with *
- empty: prefer emptyList<T>()
basics
~10 sKotlin 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 sFactory 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 linesval 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
Knows Kotlin infers the element type and you can write listOf(1, 2).
Knows mixed types give Any and you can specify the type explicitly.
Explains least-common-supertype inference, null widening to T?, the spread operator, and contrasts listOf vs listOfNotNull typing.
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