skip to content

Nullability in Generics

An unconstrained <T> has an upper bound of Any?, so it is implicitly nullable and you cannot assume a value is present. The fix is a <T : Any> bound, and knowing when to use it versus T? is a genuine senior-level question.

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

questions

5

In Kotlin, what is the default upper bound of an unbounded type parameter like <T>, and what does that imply about whether T can hold null?

level: juniorimportance: must knowfreq 70%

answer

  1. Unbounded <T> => upper bound Any?
  2. Any? = Any + null
  3. <T : Any> forbids null
  4. T inside body may be null
  5. Caller can pass Box<String?>

basics

~10 s

An unbounded <T> defaults to the upper bound Any?, which includes null. So T can hold a null value unless you restrict it.

solid answer

~40 s

When you write a generic like class Box<T> or fun <T> id(x: T): T, the type parameter T has no explicit upper bound, so Kotlin uses the implicit upper bound Any? (the type at the top of the nullable hierarchy). Because Any? includes null, T is implicitly nullable: a caller can supply Box<String?> or call id(null), and inside the generic code a value of type T may be null. This is why you cannot safely call members like x.length on a T without a null check or a smart-cast. To forbid nulls you constrain the parameter with <T : Any>, making the upper bound the non-null Any. The distinction matters because T (the parameter itself) and T? (an explicitly nullable use of T) behave differently in generic code.

code

kotlin · 6 lines
kotlin
class Box<T>(val value: T)        // T : Any? implicitly
val nullable = Box<String?>(null)  // legal

fun <T : Any> nonNullBox(v: T) = Box(v)
nonNullBox("x")                    // OK
// nonNullBox(null)               // compile error

go deeper

for a junior

Knows unbounded <T> defaults to Any? and that null is therefore allowed.

for a middle

Can show how <T : Any> forbids null and changes body assumptions, with a code example.

for a senior

Explains why the compiler treats T as possibly-null and the interaction with smart-casts and member access.

for a principal

Frames the default Any? bound as a deliberate language design choice and discusses API design implications for library type parameters.

## The implicit upper bound Every generic type parameter in Kotlin has an **upper bound** — the most general type it is allowed to be. When you declare a parameter without a bound, like `class Box<T>` or `fun <T> identity(x: T): T`, Kotlin fills in the implicit upper bound `Any?`. `Any?` is the root of Kotlin's *nullable* type hierarchy — it is `Any` (the non-null root) plus `null`. Because the bound is `Any?`, **a caller may substitute a nullable type for `T`**, and `null` itself is a legal value of `T`. ```kotlin class Box<T>(val value: T) val a = Box("hi") // T = String val b = Box<String?>(null) // T = String?, value is null — legal ``` ## Why T is treated as possibly-null inside the body Inside generic code, the compiler must assume `T` could be a nullable type, so a value of type `T` is treated as possibly null. You cannot dereference it without handling null: ```kotlin fun <T> firstChar(x: T): Char { // return x.length // ERROR: x may be null, and T isn't even necessarily String return x.toString().first() // toString() is on Any?, returns "null" for null } ``` Even the safe-call operator `?.` is meaningful here because `x` of type `T` might be null. ## Forbidding null with `<T : Any>` To say "T must be a non-null type," add the explicit bound `Any`: ```kotlin fun <T : Any> requireNonNull(x: T): T = x requireNonNull("hi") // OK, T = String // requireNonNull(null) // ERROR: null does not satisfy T : Any ``` Now callers cannot pass `Box<String?>` and cannot pass a literal `null`; inside the body `x` is known non-null. ## Key takeaways - Unbounded `<T>` == `<T : Any?>` == possibly nullable. - `<T : Any>` forbids null and makes `T` non-null inside the body. - This is purely about the *parameter*; an explicit `T?` is a separate, always-nullable use discussed elsewhere.

  • What does <T : Any> change inside the function body?
    T is known to be non-null, so values of type T can be dereferenced without a null check and smart-casts treat them as non-null.
  • Is <T> the same as <T : Any?>?
    Yes — they are equivalent; the explicit Any? bound just spells out the default.

An unbounded <T> is like a job posting with no requirements — 'null' applicants are welcome until you add 'must be non-null' (: Any).

saying these in an interview costs you the question

  • Claiming unbounded <T> defaults to Any (non-null)
  • Saying T can never be null without a bound
  • Confusing the bound Any? with the wildcard/star projection
  • Thinking <T : Any> changes the runtime type erasure behavior

context

open as a page

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%

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.

open as a page

Implement a generic extension fun <T> Iterable<T>.firstMatchingOrNull(predicate: (T) -> Boolean): T?. Explain why the return is T? and how it behaves when T is itself a nullable type.

level: middleimportance: should knowfreq 45%

basics

~20 s

Loop over the elements, return the first that matches, else null. The return is T? because there may be no match. If T is nullable, T? is still just that nullable type, so a matched null is indistinguishable from 'no match'.

open as a page

When a generic Kotlin function calls into Java, how do platform types affect nullability of T, and how can <T : Any> versus an unbounded <T> change the safety guarantees you actually get?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Java values arrive as platform types with unknown nullability, so the compiler can't enforce non-null. Declaring your parameter <T : Any> doesn't check Java-supplied nulls at the boundary — a null can slip in and fail later. Explicit checks are needed.

open as a page

When designing a generic API, when should you constrain <T : Any> versus accepting an unbounded <T> and using T? at specific positions? Discuss the trade-offs and a case where each is the right call.

level: seniorimportance: nice to knowfreq 25%

basics

~20 s

Use <T : Any> when nulls make no sense for your type and you want callers blocked from passing them. Use unbounded <T> when nullable element types are legitimate, and mark only the positions that can be null with T?.

open as a page