skip to content

How does nullability interact with generics in Kotlin? Explain what `T`, `T?`, and `T & Any` mean for a type parameter, and how to express a non-null bound.

level: seniorimportance: should knowfreq 30%

answer

  1. Bare `<T>` implicitly bounds `Any?` — may be nullable
  2. `<T : Any>` forbids nullable type arguments
  3. `T?` always nullable; idempotent
  4. `T & Any` = definitely non-null intersection
  5. `T & Any` mainly for Java/platform-type overrides

basics

~20 s

An unbounded type parameter T can itself be a nullable type, so values of T might be null. To force non-null, bound it with T : Any. Writing T? always allows null; T & Any strips nullability from a T.

solid answer

~40 s

A bare type parameter `<T>` has an implicit upper bound of `Any?`, meaning a caller can substitute a nullable type (`String?`), so a value of type `T` *may already be nullable*. That's why `fun <T> first(list: List<T>): T` can return null if `T` is `String?`. To guarantee non-null, declare `fun <T : Any> ...` — the `Any` bound forbids nullable substitutions. Inside generic code, `T?` is always nullable regardless of `T`. Kotlin also has **definitely non-nullable types** written `T & Any` (intersection with `Any`): it takes whatever `T` is and removes nullability, which is essential when overriding Java methods whose platform-typed parameter must be non-null even though `T` could be nullable. So: `T` = maybe nullable, `T?` = definitely nullable, `T & Any` = definitely non-null.

code

kotlin · 8 lines
kotlin
fun <T> maybe(x: T): T = x            // T : Any? -> T may be null
fun <T : Any> sure(x: T): T = x        // T must be non-null

fun <T> firstOr(xs: List<T>): T? =     // always nullable result
    xs.firstOrNull()

// Definitely non-nullable when overriding a generic Java method
// override fun transform(t: String & Any): String & Any = t.trim()

go deeper

for a junior

Likely unaware the default bound is Any?; may assume generics are non-null.

for a middle

Knows to add : Any for non-null type parameters and that T? is nullable.

for a senior

Explains the implicit Any? bound, T : Any constraint, and T & Any definitely-non-null types with the Java-interop motivation.

for a principal

Designs generic APIs with deliberate nullability bounds and uses T & Any correctly at platform-type boundaries without leaking nullability.

## Default upper bound is nullable An unconstrained type parameter implicitly extends `Any?`: ```kotlin fun <T> identity(x: T): T = x // T : Any? implicitly identity<String?>(null) // legal: T = String? ``` So **a value of type `T` may itself be null**, because the caller can pick a nullable type argument. This surprises people: `fun <T> head(xs: List<T>): T` is not a non-null guarantee. ## Forcing non-null type arguments: `T : Any` Give the parameter an `Any` upper bound to forbid nullable substitutions: ```kotlin fun <T : Any> firstNonNull(xs: List<T>): T = xs.first() firstNonNull(listOf(1, 2)) // OK, T = Int // firstNonNull(listOf<Int?>(1)) // ERROR: Int? is not a subtype of Any ``` Now `T` can only be a non-null type, so the returned `T` is guaranteed non-null. ## `T?` inside generic code Writing `T?` **always** adds nullability on top of `T`. If `T` is already `String?`, then `T?` is still just `String?` (the `?` is idempotent), but if `T` is `String`, `T?` is `String?`. Use it when an operation may legitimately produce absence: ```kotlin fun <T> List<T>.firstOrNullCopy(): T? = if (isEmpty()) null else this[0] ``` ## Definitely non-nullable types: `T & Any` Introduced for safe Java interop, `T & Any` is the **intersection** of `T` with `Any` — it strips nullability from `T` whatever `T` is. The classic need is overriding a generic Java method whose argument is a *platform type* but must be treated as non-null: ```kotlin // Java: interface Transformer<T> { T transform(T t); } class NonNullTransformer : Transformer<String?> { // 'T & Any' makes the parameter definitely non-null override fun transform(t: String & Any): String & Any = t.uppercase() } ``` Without `T & Any` you'd be forced to accept a nullable parameter even when the contract is non-null. ## Summary table | Form | Meaning | |---|---| | `<T>` | upper bound `Any?` — `T` may be nullable | | `<T : Any>` | type argument must be non-null | | `T?` | always nullable (idempotent on already-nullable `T`) | | `T & Any` | definitely non-nullable — strip nullability from `T` | ## Practical guidance - Add `: Any` whenever a generic API must never see or return null. - Reach for `T & Any` mainly at Java boundaries / platform types, not in pure-Kotlin code. - Remember `T?` does not *guarantee* anything new when `T` is already nullable.

  • If I write `fun <T> first(xs: List<T>): T`, is the result guaranteed non-null?
    No. `T` implicitly extends `Any?`, so a caller can pass `List<String?>`, making the result possibly null. Use `<T : Any>` to guarantee non-null.
  • When would you actually need `T & Any`?
    Mostly at the Java interop boundary — when overriding a generic Java method whose platform-typed parameter/return must be treated as definitely non-null even though `T` itself could be nullable.

saying these in an interview costs you the question

  • Assuming a bare `T` is always non-null
  • Thinking `<T : Any?>` adds a meaningful constraint (it's the default)
  • Not knowing `T & Any` exists or what intersection nullability means
  • Claiming `T?` makes an already-nullable `T` 'doubly nullable'

context