skip to content

Inside generic code, how does using T differ from using T? when the parameter is declared <T : Any>? Show how each affects what values are accepted and returned.

level: middleimportance: must knowfreq 60%

answer

  1. T = non-null under <T : Any>
  2. T? = T-or-null for that position
  3. Map.get returns V? though V non-null
  4. T? never double-nullable (no String??)
  5. Input T, output T? for 'not found'

basics

~10 s

With <T : Any>, T is always non-null, while T? adds nullability back for that specific use. So T rejects null but T? accepts it, even when T itself cannot be null.

solid answer

~40 s

Given <T : Any>, the bare type T is non-null everywhere it appears, so parameters and returns of type T reject null. Writing T? on a specific position re-introduces nullability for that position only: a parameter of type T? accepts null and a return of type T? may produce null, regardless of T being constrained to Any. This lets you, for example, take a non-null T but return T? to signal 'not found'. The classic example is Map<K, V>.get returning V? even when V is non-null. Crucially, T? means 'T-or-null', so even with an unbounded <T> (already Any?), T? is still a distinct, explicitly-nullable type — but the practical difference is clearest under <T : Any>, where T is non-null and T? is the deliberately nullable variant.

code

kotlin · 10 lines
kotlin
class Registry<T : Any> {
    private val items = mutableListOf<T>()
    fun add(item: T) { items += item }            // rejects null
    fun firstOrNull(): T? = items.firstOrNull()    // may be null
}

val r = Registry<String>()
r.add("a")            // ok
// r.add(null)        // error: T is non-null
val x: String? = r.firstOrNull()  // must handle null

go deeper

for a junior

Recognizes that T? allows null and T does not under <T : Any>.

for a middle

Demonstrates input-T / output-T? API shaping and cites Map.get returning V?.

for a senior

Explains nullability flattening (no String??) and how smart-casts make T? ergonomic at call sites.

for a principal

Discusses how this T vs T? distinction drives null-correct collection and cache API contracts at scale.

## Two ways to spell a generic type Inside generic code you can refer to a type parameter as either `T` or `T?`. They are *different types*: - `T` — the parameter as constrained. Under `<T : Any>`, `T` is **non-null**. - `T?` — the **nullable version** of whatever `T` is: 'a `T`, or `null`'. ## Under `<T : Any>` the difference is sharp ```kotlin class Cache<T : Any> { private val map = HashMap<String, T>() fun put(key: String, value: T) { map[key] = value } // value: non-null T fun get(key: String): T? = map[key] // may be null: not found } ``` - `put` takes `T` — you **cannot** pass `null`. - `get` returns `T?` — the caller must handle `null` (a miss), even though stored values are non-null. This is exactly how the standard library is shaped: `Map<K, V>.get(key): V?` returns nullable even when `V` is non-null, because a missing key yields `null`. ## Under an unbounded `<T>` (== `<T : Any?>`) Here `T` is already nullable (`Any?` bound). `T?` is still a *distinct* type, but flattening rules apply: if `T` is substituted with `String?`, then `T?` is still just `String?` (Kotlin does not produce `String??`). So `T?` never adds a second layer of nullability. ```kotlin fun <T> echo(x: T): T? = x // if T = String?, return type is String? ``` ## Why this matters for API design - Use `T` for inputs you require to be present. - Use `T?` for outputs that may legitimately be absent (lookups, `firstOrNull`, etc.). - Constrain with `<T : Any>` when you want callers to be forced to pass non-null, then opt back into nullability only where you mean it with `T?`. ## Smart-casts A `T?` value can be smart-cast to `T` after a null check (`if (x != null) { /* x: T */ }`), which is why returning `T?` and checking it at the call site is ergonomic.

  • If T = String?, what is T? ?
    Still String? — Kotlin flattens nullability, there is no String??.
  • Why does Map.get return V? even when V is non-null?
    Because a missing key must be representable, and null is the absence signal; V itself stays non-null for stored values.

T is a required field on a form; T? is the same field marked optional — the column type didn't change, the requirement did.

saying these in an interview costs you the question

  • Saying T and T? are interchangeable
  • Believing T? produces a doubly-nullable String??
  • Thinking <T : Any> makes T? also non-null
  • Not knowing Map.get returns a nullable value
  • Claiming you can pass null to a T parameter under <T : Any>

context