skip to content

Star-Projection

List<*> says the type argument exists but is unknown, giving you Any? out and nothing in. Interviewers ask when a star projection is enough versus when you actually need an explicit in or out projection.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

What does the star projection `List<*>` mean in Kotlin, and what can you safely do with such a value?

level: juniorimportance: must knowfreq 70%

answer

  1. `*` = unknown-but-safe type argument
  2. Reads come back as Any? for List<*>
  3. Can't add a concrete element to MutableList<*>
  4. Kotlin's safe replacement for Java raw types
  5. Use when you don't depend on the concrete type

basics

~20 s

List<*> means a list of some unknown element type. You can read items as Any? and call type-agnostic members like size, but you cannot safely add a specific element because the real type is unknown.

solid answer

~40 s

`List<*>` is the star projection: a `List` whose element type is unknown but still type-safe. The `*` says "I don't know or care which `T` this is." You can read elements (they come back as `Any?`) and use members that don't depend on `T` in an unsafe position, like `size` or `isEmpty()`. You cannot call `add(x: T)`-style methods because the compiler can't prove `x` matches the hidden `T`. For a read-only `List`, `List<*>` behaves like `List<Any?>` for reads. It's distinct from `List<Any>` (a list known to hold `Any`) and from the raw types Kotlin doesn't have. Use it when you genuinely don't know the argument, e.g. `fun printAll(c: Collection<*>)`.

code

kotlin · 9 lines
kotlin
fun countItems(c: Collection<*>): Int = c.size

fun firstOrNothing(list: List<*>): Any? = list.firstOrNull()

fun main() {
    println(countItems(listOf(1, 2, 3)))   // 3
    println(countItems(setOf("a", "b")))   // 2
    println(firstOrNothing(listOf(true)))  // true (typed as Any?)
}

go deeper

for a junior

Knows * means an unknown element type and that you can read as Any? and call size.

for a middle

Explains why you can't write to a MutableList<*> and that it's Kotlin's safe alternative to raw types.

for a senior

Distinguishes List<*> from List<Any?> and ties safety to projected bounds.

for a principal

Frames star projection as existential typing and discusses API-design implications of accepting * vs a generic parameter.

## What the star projection is The **star projection** is written with a single asterisk as the type argument, e.g. `List<*>`, `Map<String, *>`, `Comparable<*>`. It means: *this is a generic type whose actual type argument is unknown, but I still want the result to be fully type-safe.* Think of `*` as "some specific type exists here, I just don't know which one." It is **not** `List<Any?>` declared explicitly, and Kotlin has **no raw types** like Java's `List` — `*` is the safe, principled replacement. ## What you can and cannot do For a read-only interface like `List<out E>`: - **Reads are allowed**: `val first = list[0]` gives back `Any?` (the safe upper bound of `out E`). - **`size`, `isEmpty()`, `contains(Any?)`** work because they don't put an unknown `E` into an `in` position. For a mutable type like `MutableList<E>`: - **Writes are forbidden**: `mutableList.add("x")` does **not** compile, because the compiler cannot prove `"x"` matches the hidden element type. - You can still call `removeAt`/`clear` because they don't take an `E` as input. ```kotlin fun describe(list: List<*>) { println("size = ${list.size}") // OK: no E involved val item: Any? = list.firstOrNull() // OK: reads as Any? // (list as MutableList).add(1) // not provably safe } describe(listOf(1, 2, 3)) describe(listOf("a", "b")) ``` ## Why it's safe The star projection works because Kotlin substitutes safe **bounds** for the unknown argument. The net effect: you may consume values that come *out* (typed as the upper bound) but you may not feed values *in*. ## When to reach for it Use `*` when the function truly doesn't depend on the concrete argument — counting, logging, emptiness checks, or passing through. If you DO need the type, use a generic type parameter (`fun <T> foo(list: List<T>)`) or an explicit projection (`List<out Number>`) instead.

  • What type does `list[0]` have when `list: List<*>`?
    `Any?` — the safe upper bound for an unknown `out E`, since elements can be read but their exact type is unknown and may be nullable.
  • Is `List<*>` the same as `List<Any?>`?
    Not exactly. `List<Any?>` is a concrete known type, while `List<*>` records that there is some specific unknown `T`. For read-only `List` the reading behavior coincides, but they are different declarations and `*` also applies safely to invariant/in positions where `Any?` would not.

Like a sealed mystery box labeled only 'contains items' — you can take things out and treat them generically, but you can't safely put a specific thing in because you don't know what's already inside.

saying these in an interview costs you the question

  • Saying `List<*>` means 'a list of Any' that you can add anything to
  • Confusing `List<*>` with Java raw types (claiming it's unsafe/untyped)
  • Thinking you can call `add("x")` on a `MutableList<*>`
  • Believing the star is just shorthand for `List<Any?>` in all positions
  • Saying reads from `List<*>` return the concrete element type

context

open as a page

Explain how the star projection maps to bounds: for `Foo<out T : TUpper>` and `Foo<in T>`, what does `Foo<*>` become for reading and writing?

level: middleimportance: must knowfreq 60%

basics

~20 s

For an out parameter, Foo<*> reads as the parameter's upper bound (Any? if none). For an in parameter, Foo<*> only accepts Nothing for writes, so you effectively can't write. Star = read at upper bound, write nothing.

open as a page

How does star projection interact with runtime type checks (`is`/`as`) and generic erasure? Why does `x is List<*>` compile while `x is List<String>` does not?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Kotlin erases generic type arguments at runtime, so you can't check is List<String> — the element type isn't there to check. The star projection is List<*> only checks that it's a List at all, which is exactly what's verifiable after erasure.

open as a page

When should you use a star projection `List<*>` versus an explicit use-site projection like `List<out Number>` or a generic type parameter `<T>`?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Use * only when you truly don't know or need the type — pure read/passthrough/counting. Use an explicit projection (out Number) when you need a useful upper bound. Use a generic <T> when callers need to relate input and output types.

open as a page

For a class with a recursive (F-bounded) parameter like `class Node<T : Comparable<T>>`, what does `Node<*>` read at, and what subtle limitations does star projection impose here?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

With a self-referential bound like T : Comparable<T>, Node<*> projects T to that bound, but the bound mentions T again, so it's star-projected too: you read at Comparable<*>. You can read generically but can't safely compare two different starred nodes.

open as a page