skip to content

Explain the precise semantics of `T & Any`: what is its erasure, why does the compiler sometimes warn, and where is it disallowed?

level: seniorimportance: should knowfreq 14%

answer

  1. Only `<typeParam> & Any` is legal; no arbitrary intersections
  2. Requires a nullable upper bound, else redundant-warning
  3. Compile error on String & Any / Int & Any / & non-Any
  4. Erases like T; no auto runtime null check
  5. Static contract; ?: or !! do the runtime work

basics

~20 s

T & Any is a compile-time-only non-null version of T. At runtime it erases to T's bound. The compiler warns when it's redundant (T already non-null) and forbids it where T isn't a type parameter with a nullable bound.

solid answer

~60 s

`T & Any` is an **intersection type** between a type parameter `T` and `Any`, valid only in the *definitely-non-null* position — i.e., `T` must be a type parameter whose upper bound is nullable. Semantically it denotes the non-nullable form of whatever `T` is instantiated with; nullability is part of Kotlin's type system, so this is purely a compile-time refinement. At runtime, generics are **erased** to their bounds on the JVM, so `T & Any` carries no extra runtime token beyond `T`'s erasure — there is no inserted null check from the type alone (a check only happens if you write one, e.g. `?:` or a not-null assertion). The compiler **warns** (`'X & Any' is unnecessary`) when `T` is already non-null, because the intersection adds nothing. It is a **compile error** to apply `& Any` to something that is not a nullable-bounded type parameter — e.g. `String & Any`, `Int & Any`, or `T & Any` where `T : Any`. You also can't form arbitrary intersections; `&` is reserved exclusively for this `T & Any` shape.

code

kotlin · 6 lines
kotlin
fun <T> ok(x: T & Any) {}          // OK: default bound is Any?
fun <T : Any> redundant(x: T & Any) {} // warning: unnecessary

// val bad: String & Any = ""        // compile error: not a type parameter

fun <T> coerce(x: T): T & Any = x ?: error("null") // ?: is the runtime check

go deeper

for a junior

Can state it's compile-time and not a runtime check.

for a middle

Knows the redundancy warning and that the left must be a type parameter.

for a senior

Explains erasure, the exact grammar restriction (<typeParam> & Any), and that ?:/!! are what enforce at runtime.

for a principal

Reasons about how the static contract interacts with platform types, unchecked Java nulls, and where NPEs can still surface despite the non-null type.

## Exact rules of the notation The grammar allows `&` **only** as `<typeParameter> & Any`. You cannot write `Foo & Bar`, `String & Any`, or chain it. The left side must be a **type parameter** and the right side must be exactly `Any`. ### Requirement: nullable bound `T & Any` is meaningful only when `T`'s upper bound is nullable. By default a parameter `<T>` has bound `Any?`, so `T & Any` is allowed. With `<T : Any>` the bound is already non-null: ```kotlin fun <T : Any> f(x: T & Any) {} // warning: 'T & Any' is unnecessary; T is already non-null fun <T> g(x: T & Any) {} // OK: default bound Any? is nullable ``` ### Disallowed forms (compile errors) ```kotlin val a: String & Any = "" // error: not a type parameter val b: Int & Any = 1 // error // fun <T> h(): T & Bar // error: right side must be Any ``` ## Runtime view: erasure Kotlin generics follow JVM **type erasure**: at runtime a value of type `T` (or `T & Any`) is represented by `T`'s erased bound (often `Object`/`Any`). `T & Any` does **not** emit an automatic runtime null check. If a null sneaks in via unchecked Java code, you only get an exception where an *actual* null operation occurs, not from the type annotation itself. The non-null guarantee is a **static** contract. ```kotlin fun <T> coerce(x: T): T & Any = x ?: error("null") // the ?: is what checks at runtime ``` ## Why `Any` specifically `Any` is the least non-nullable supertype: intersecting any (possibly nullable) type with it yields the non-null version while preserving the type's own identity (`String? & Any == String`). Using `Any` rather than `T`'s bound keeps it generic and minimal. ## Interaction with platform types When a value comes from Java as a platform type `T!`, assigning it to a `T & Any`-typed slot is allowed but the usual platform-type rules apply: the compiler trusts you, and a real null would surface as a `NullPointerException` at the point of use. ## Keywords/APIs recap - intersection type, `&` operator (restricted form) - `Any` / `Any?` roots, upper bound - JVM type erasure, platform type `T!` - `?:` elvis and `!!` not-null assertion as the *runtime* enforcers in bodies

  • Does `T & Any` insert a runtime null check by itself?
    No. It's a static type. Runtime enforcement only happens where you write a check (`?:`, `!!`, `requireNotNull`). Generics erase to bounds on the JVM.
  • Why can't you write `String & Any` or `Foo & Bar`?
    The `&` form is reserved for definitely-non-null types: left side must be a type parameter and right side must be exactly `Any`. Concrete types and arbitrary intersections are not allowed.

saying these in an interview costs you the question

  • Claiming `T & Any` adds an implicit runtime null check from the type alone
  • Thinking arbitrary intersection types like `A & B` are supported
  • Not knowing it warns when the bound is already non-null
  • Believing erasure preserves a special `& Any` token at runtime
  • Confusing the static guarantee with platform-type safety

context