skip to content

How do you write a generic function that refuses nullable type arguments, and what exactly does <T : Any> guarantee at compile time?

level: middleimportance: must knowfreq 60%

answer

  1. <T : Any> = non-null top type
  2. String? not a subtype of Any
  3. Body value of type T is non-null
  4. T? re-adds nullability per parameter
  5. Compile-time only, not runtime check

basics

~10 s

Add the bound <T : Any>. Then callers cannot use a nullable type like String? for T, so the value is guaranteed non-null and you can use it without null checks.

solid answer

~40 s

Declaring `<T : Any>` sets the upper bound to `Any`, the non-null top type. Because a nullable type such as `String?` is NOT a subtype of `Any` (it is a subtype of `Any?`), the compiler rejects any attempt to instantiate `T` with a nullable type. Inside the body, a parameter of type `T` is therefore non-null — no `?.`, `!!`, or null check needed. This is the idiomatic way to express 'works for any non-null value'. Note the subtlety: even with `<T : Any>`, declaring a parameter as `T?` re-introduces nullability for that specific parameter. Standard library examples include `checkNotNull`/`requireNotNull` returning `T : Any`, and `let`-style helpers. The guarantee is purely static: it constrains the call site's type argument, not runtime data validation.

code

kotlin · 4 lines
kotlin
fun <T : Any> unwrapAll(items: List<T?>): List<T> = items.filterNotNull()

val clean: List<String> = unwrapAll(listOf("a", null, "b"))
// T = String (non-null); list elements typed T? at the call boundary

go deeper

for a junior

Recognizes that <T : Any> blocks null but may not articulate the Any vs Any? subtyping.

for a middle

Explains that String? is not a subtype of Any and that a T value is then non-null in the body.

for a senior

Knows the T? re-nullability subtlety and maps it to stdlib helpers like requireNotNull.

for a principal

Reasons about API contracts: when to expose <T : Any> vs <T> in a public library and the platform-type interactions from Java interop.

## The goal You want a generic that accepts *any* type but never a nullable one. The tool is the **upper bound** `<T : Any>`. ## Why `Any` forbids null Kotlin's type hierarchy has two top types: - `Any?` — the nullable top type; *every* type, nullable or not, is a subtype. - `Any` — the **non-null** top type; only non-null types are subtypes. `String?` is a subtype of `Any?` but **not** of `Any`. So when the bound is `Any`, the compiler rejects `f<String?>(...)`: ```kotlin fun <T : Any> store(value: T): T = value store("hi") // OK, T = String store(42) // OK, T = Int // store<String?>(null) // ERROR: String? is not a subtype of Any ``` ## What you gain in the body With `<T : Any>`, a parameter declared as `value: T` is statically non-null. You can call its members directly without `?.` or `!!`: ```kotlin fun <T : Any> describe(value: T): String = value.toString() // no null check needed ``` ## The `T?` subtlety The bound constrains what `T` itself can be, but you can still *opt back into* nullability by writing `T?`: ```kotlin fun <T : Any> firstOrNull(items: List<T>): T? = if (items.isEmpty()) null else items[0] ``` Here `T` is non-null (no null elements), yet the return type `T?` allows the function to return `null`. ## Standard-library parallels `requireNotNull(value: T?): T` and `checkNotNull` take a nullable input and return `T` (a non-null type) — they rely on `T : Any` semantics to express 'the result is non-null'. ## Guarantee scope This is a **compile-time** constraint on the type argument. It is not runtime validation: nothing scans data for nulls; the compiler simply refuses nullable type arguments and lets you treat `T` values as non-null.

  • With <T : Any>, can a parameter still legitimately be nullable?
    Yes — write the parameter type as T?. The bound restricts T itself to non-null, but T? explicitly re-introduces nullability for that position.
  • Does <T : Any> do any runtime null validation on the arguments?
    No. It is a purely static constraint on the type argument; it prevents nullable type arguments at compile time but does not insert runtime null checks.

<T : Any> is a bouncer who turns away 'nobody' (null) at the door; once inside, you never have to ask 'is anyone there?'.

saying these in an interview costs you the question

  • Claiming String? is a subtype of Any
  • Saying <T : Any> inserts runtime null checks
  • Believing you still need ?. on a T value when bound is Any
  • Confusing <T : Any> with <T : Any?>
  • Thinking the bound prevents T? parameter declarations

context