Given `listOf("a", null, "b")` and `listOf(1, "x")`, what element types does Kotlin infer, and how does nullable unification combine with generic variance to produce them?
answer
- listOf infers T = LUB of all elements
- null among strings -> List<String?>
- Int + String -> List<Any> (intersection)
- List<out E> is covariant: List<String> <: List<String?>
- List<String?> vs List<String>? are different
basics
~20 slistOf("a", null, "b") is List<String?> because the elements unify to String?. listOf(1, "x") is List<Any> because Int and String share only the supertype Any (or its intersection). The common type of all elements becomes the element type.
solid answer
~30 s`listOf(vararg elements: T)` infers `T` as the **least upper bound** of all argument types. For `listOf("a", null, "b")`: arguments are `String`, `Nothing?`, `String`; `LUB(String, Nothing?) = String?`, so the result is `List<String?>`. For `listOf(1, "x")`: `Int` and `String` are unrelated, so the LUB climbs to their common supertype `Any` (in practice the intersection `Comparable<*> & Serializable`, but conceptually `Any`), giving `List<Any>`. Because `List` is **covariant** (`interface List<out E>`), a `List<String>` is also a `List<String?>` and a `List<Nothing>` is a `List<Any>` — variance lets the inferred element type widen naturally. The same LUB machinery that types `if/else` branches types vararg/collection literals.
code
kotlin · 5 linesval a = listOf("a", null, "b") // List<String?>
val b = listOf(1, "x") // List<Any>
val c = listOf(1, 2, 3) // List<Int>
val e: List<String?> = listOf("x") // List<String> widens via out E
val f: List<Any> = emptyList<Nothing>() // List<Nothing> <: List<Any>go deeper
Recognizes that adding null gives a list of nullable elements (List<String?>).
Explains element type = LUB of elements and that unrelated types climb to Any, plus the List<String?> vs List<String>? distinction.
Ties LUB inference to covariance (out E) enabling sound widening like List<String> <: List<String?>.
Discusses intersection-type computation for unrelated elements and design tradeoffs of declaration-site variance in the standard library collections.
## Vararg inference uses LUB on elements `listOf` is `fun <T> listOf(vararg elements: T): List<T>`. The compiler infers the single type parameter `T` as the **least upper bound (LUB)** — the most specific common supertype — of every argument's type. ## Case 1: a null among strings ```kotlin val xs = listOf("a", null, "b") ``` - `"a"`, `"b"`: type `String`. - `null`: type `Nothing?`. - `LUB(String, Nothing?, String) = String?`. Result: `List<String?>`. The single `null` lifts the element type into the nullable half of the lattice — exactly the same upward unification as in `if/else`. ## Case 2: unrelated element types ```kotlin val ys = listOf(1, "x") ``` - `1`: `Int`; `"x"`: `String`. - These are unrelated, so the LUB climbs to their nearest common supertype. Conceptually that is `Any`; the compiler may compute the **intersection of shared supertypes** (`Comparable<*> & java.io.Serializable`), but for an explicit annotation you'd write `List<Any>`. Result: `List<Any>` (no nullability, since neither element is null). ## How variance participates `List` is declared **covariant**: `public interface List<out E>`. Covariance (`out`) means: if `A <: B` then `List<A> <: List<B>`. This is what lets the inferred element type *widen*: - `List<String> <: List<String?>` (since `String <: String?`), so a list of strings is usable as a list of nullable strings. - `List<Nothing> <: List<Any>`, so `emptyList<Nothing>()` works anywhere a `List<Any>` is needed. Without covariance, widening the element type would be unsound; with it, the LUB-inferred type integrates cleanly. ## A nullable-collection vs collection-of-nullable distinction Be careful: - `List<String?>` — a non-null list whose **elements** may be null. - `List<String>?` — a **list reference** that may be null, elements non-null. - `List<String?>?` — both may be null. Unification of elements only affects the *element* type (`String?`); the outer list reference here is non-null. ## Summary Element type = LUB of all elements; a single `null` (type `Nothing?`) makes it nullable; unrelated types climb toward `Any`; covariance (`out E`) makes the resulting widening type-safe.
- What is the difference between List<String?> and List<String>?List<String?> is a non-null list whose elements may each be null. List<String>? is a possibly-null reference to a list of non-null strings. The ? attaches to different positions.
- Why can a List<String> be passed where a List<String?> is expected?List is covariant (out E), and String <: String?, so List<String> <: List<String?>. Covariance propagates the element subtype relation to the container.
saying these in an interview costs you the question
- Saying listOf("a", null) is a compile error
- Inferring List<String> (dropping nullability) for listOf("a", null)
- Confusing List<String?> with List<String>?
- Claiming List is invariant and so cannot widen the element type