When should you use a star projection `List<*>` versus an explicit use-site projection like `List<out Number>` or a generic type parameter `<T>`?
answer
- Relate in/out positions -> generic <T>
- Need a useful bound -> out Bound / in Bound
- Type truly irrelevant -> star *
- is concise but lossy; casting after * = wrong tool
- Star only some args: Map<String, *>
basics
~20 sUse * 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.
solid answer
~50 sChoose by how much you need to know about the type: - **Star `List<*>`**: you don't care about the element type at all. Best for utility/diagnostic code (`size`, logging, equality). It throws away type information, so anything you read is the (possibly wide) bound. - **Explicit use-site projection `List<out Number>`**: you don't need the exact type but you *do* need a meaningful bound — reads should be `Number`, and you want to accept `List<Int>`, `List<Double>`, etc. Strictly more informative than `*` when a bound exists. - **Generic parameter `fun <T> firstAndAdd(list: MutableList<T>, e: T)`**: you must *relate* multiple positions — read a `T` and write a `T`, or return the same `T` you accepted. `*` can't do this because it forgets the type; a captured type variable can. Rule of thumb: reach for `*` last — only when there is genuinely nothing useful to say about the type.
code
kotlin · 12 lines// * — type irrelevant
fun describe(c: Collection<*>) = "size=${c.size}"
// explicit projection — bound is useful
fun total(xs: List<out Number>) = xs.sumOf { it.toDouble() }
// generic — must relate read and write
fun <T> rotate(list: MutableList<T>) {
if (list.isEmpty()) return
val head = list.removeAt(0)
list.add(head)
}go deeper
Knows * is for 'don't care about the type' cases like counting.
Picks explicit out Bound over * when a bound helps and recognizes the equivalence to out Any?.
Correctly chooses generic <T> to relate positions and refactors a stuck * API into a parameterized one.
Reasons about API ergonomics, information loss, and when to expose <T> vs projections in a public surface.
## The decision ladder Pick the *most* informative option your code can justify, then fall back: 1. **Generic type parameter `<T>`** — when you need to *connect* type positions. If a value flows in and the same type flows out, or two arguments must share a type, you need a named `T`: ```kotlin fun <T> swapFirst(list: MutableList<T>, e: T): T { val old = list[0] list[0] = e // works: list and e share T return old } ``` Star projection cannot express this — `MutableList<*>` forbids the write and forgets that the read and the argument share a type. 2. **Explicit use-site projection `out`/`in`** — when you don't need the exact type but a *bound* is useful: ```kotlin fun sumAll(nums: List<out Number>): Double = nums.sumOf { it.toDouble() } // reads as Number ``` `List<out Number>` accepts `List<Int>`, `List<Long>`, etc., and reads come back as `Number` — richer than `Any?`. Use `in` for the symmetric consumer case (`MutableList<in String>` to add strings into a wider list). 3. **Star projection `<*>`** — when the type is genuinely irrelevant: ```kotlin fun logSize(c: Collection<*>) = println("items: ${c.size}") fun isEmpty2(m: Map<*, *>) = m.isEmpty() ``` Use `*` for counting, emptiness, identity/equality, or pure passthrough where you never touch a `T`-typed value meaningfully. ## Equivalences and subtleties - For an unbounded `out` parameter, `Foo<*>` is **equivalent** to `Foo<out Any?>`. Prefer the bare `*` for readability; prefer `out Bound` whenever a tighter bound exists. - `*` is concise but **lossy**. If you find yourself casting the result of a starred read, you probably wanted `out Bound` or `<T>`. - You can mix: `Map<String, *>` keeps the key type and stars only the value — better than `Map<*, *>` when keys matter. - For an `in`-projected consumer you usually want explicit `in T` (so you can write `T`), not `*` (which projects writes to `Nothing`). ## Anti-patterns - Using `*` then immediately `as`-casting back to a concrete type defeats the safety and signals you wanted a real generic parameter. - Declaring `<T>` with `T` used in exactly one position and never related elsewhere — that's an unnecessary type parameter; a projection (or `*`) is cleaner.
- Is `List<*>` ever preferable to `List<out Any?>`?They're equivalent for an unbounded `out` parameter; `*` is just more concise and idiomatic. Prefer `out Bound` only when a tighter bound than `Any?` exists.
- You wrote `fun copy(src: List<*>, dst: MutableList<*>)` and can't add elements. How do you fix it?Introduce a shared type parameter: `fun <T> copy(src: List<T>, dst: MutableList<in T>)`. Star forgets that source and destination share a type; a named `T` (with `in T` on the destination) restores the safe write.
Labeling a parcel: <T> writes the exact contents so you can re-pack the same thing; out Number writes a category; * writes only 'parcel' — fine if you never open it.
saying these in an interview costs you the question
- Defaulting to `*` everywhere and casting reads back
- Using `*` when input and output must share a type
- Claiming `out Number` and `*` are equally informative when a bound exists
- Adding an unused generic parameter where `*` would suffice
- Not knowing `*` equals `out Any?` for unbounded covariant params