skip to content

Special Types & the Type Lattice

The types that anchor Kotlin's lattice — Any, Unit, Nothing and their nullable forms — plus the runtime checks and casts that move between types. These show up in interviews as 'what does this function actually return' questions.

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

explore

questions

30

In Kotlin, what is `Any`, and how does it relate to `Any?`? Which one is the true root of the whole type system?

level: juniorimportance: must knowfreq 70%

answer

  1. Any = root of non-null types
  2. Any? = Any + null = absolute top
  3. Int <: Any <: Any?
  4. Only 3 members: equals/hashCode/toString
  5. Any can't hold null

basics

~20 s

Any is the parent of every non-null type. Any? adds the possibility of null, so it sits above Any and is the top of the whole type system — every value of any type is an Any?.

solid answer

~30 s

`kotlin.Any` is the supertype of all non-nullable types: `Int`, `String`, your classes, etc. all ultimately extend `Any`. But `Any` cannot hold `null`. `Any?` is `Any` plus null, and it is the absolute top type — every Kotlin value, nullable or not, is assignable to `Any?`. So the lattice is: a concrete type → `Any` → `Any?`. A function returning `Any` promises non-null; `Any?` allows null. `Any` declares only three open members — `equals()`, `hashCode()`, `toString()` — so it is far slimmer than Java's `Object` (no `wait`/`notify`/`getClass`). Use `Any` when you need a heterogeneous non-null reference, `Any?` when null is legitimate.

code

kotlin · 5 lines
kotlin
val items: List<Any?> = listOf(1, "two", null, 3.0)
for (it in items) {
    // every element is at least an Any?
    println(it?.toString() ?: "<null>")
}

go deeper

for a junior

Knows Any is the base of non-null types and Any? allows null.

for a middle

States the subtype chain Int <: Any <: Any? and that Any? is the true top.

for a senior

Frames Any vs Any? as an API null-safety contract and lists the three members precisely.

for a principal

Connects this to the full lattice (Nothing at the bottom, Any? at the top) and to variance/generic bound design (<T : Any> to forbid null).

## What `Any` is `kotlin.Any` is the **root of the non-nullable type hierarchy**. Every class you declare implicitly extends `Any` if you don't name another superclass, and every built-in non-null type (`Int`, `Boolean`, `String`, `List<T>`, …) is ultimately a subtype of `Any`. So a variable of type `Any` can reference *any non-null value*. ```kotlin val a: Any = 42 // Int is a subtype of Any val b: Any = "hi" // String is a subtype of Any // val c: Any = null // ERROR: Any is non-nullable ``` ## `Any?` is the real top Kotlin distinguishes a type `T` from its **nullable** form `T?`. `Any?` means "`Any` or `null`". Because *every* value — including `null` — is an `Any?`, **`Any?` is the single top type of the entire type lattice**: ```kotlin val x: Any? = null // OK val y: Any? = 42 // OK ``` The relationship is a subtype chain: `Int <: Any <: Any?`. `Any` is a subtype of `Any?` (you can always widen a non-null value to nullable), but not vice-versa. ## The three members on `Any` `Any` declares exactly three `open` functions, and nothing else: - `equals(other: Any?): Boolean` - `hashCode(): Int` - `toString(): String` That means any value, via `Any`, supports equality, hashing, and string conversion — and nothing more. There is no `getClass`, `wait`, or `notify` directly on `Any`. ## Why the distinction matters Returning `Any` vs `Any?` is a **contract**: `Any` guarantees non-null to the caller, so they don't need a null check; `Any?` forces the caller to handle null (smart-cast, `?.`, `!!`). Picking the right one is part of designing a null-safe API.

  • Can you assign a `String?` to an `Any`?
    No — `String?` may be null, and `Any` is non-nullable, so the compiler rejects it. You'd assign it to `Any?`, or smart-cast/`!!` it to a non-null `String` first.
  • Is `Nothing` a subtype of `Any`?
    Yes. `Nothing` is the bottom type — a subtype of every type, including `Any`. So `Nothing <: Any <: Any?`.

Any is 'any real thing'; Any? is 'any real thing, or nothing at all'.

saying these in an interview costs you the question

  • Saying `Any` can hold `null`
  • Calling `Any` the top of the entire lattice (that's `Any?`)
  • Claiming `Any` has methods like `getClass()` or `wait()`
  • Thinking `Any` and `Object` are identical
  • Not knowing `Any?` is a supertype of `Any`

context

open as a page

What is the difference between the as and as? cast operators in Kotlin, and what happens when each fails?

level: juniorimportance: must knowfreq 75%

basics

~20 s

as forces a value to a type and crashes with an error if it is not that type. as? tries the same but gives back null instead of crashing when the value does not match.

open as a page

What do the `is` and `!is` operators do in Kotlin, and what is a smart cast?

level: juniorimportance: must knowfreq 85%

basics

~20 s

is checks at runtime whether a value is of a given type; !is is the negation. After a successful is check, Kotlin automatically treats the variable as that type, so you can use it without an extra cast. That automatic narrowing is a smart cast.

open as a page

What is kotlin.Nothing, and how does it differ from Unit?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Nothing is a type with no values at all; it marks code that never returns normally, like throwing an exception. Unit means a function returns but gives back nothing useful, like void.

open as a page

In Kotlin's type system, what is the relationship between a type T and its nullable counterpart T?, and why does assigning a String to a String? compile without any conversion?

level: juniorimportance: must knowfreq 70%

basics

~20 s

T is a subtype of T?. Every non-null String is also a valid String?, so a String fits anywhere a String? is expected, just like a child fits where a parent is asked for. No conversion happens.

open as a page

What is kotlin.Unit, and what does a Kotlin function return when you don't declare a return type?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Unit is Kotlin's type for functions that do work but give back no useful value. If you don't write a return type, the function returns Unit automatically.

open as a page

`Any` declares `equals` and `hashCode`. What is the contract between them, and how does a Kotlin `data class` satisfy it?

level: middleimportance: must knowfreq 75%

basics

~10 s

If two objects are equal by equals, they must return the same hashCode. data class auto-generates both from the constructor properties, so equal data objects always share a hash code.

open as a page

Why does `value as List<String>` produce an 'unchecked cast' warning in Kotlin, and what are the consequences at runtime?

level: middleimportance: must knowfreq 60%

basics

~20 s

At runtime the program does not remember the type inside the list, only that it is a list. So Kotlin can check it is a List but not that it holds Strings. It warns because the cast might wrongly succeed and crash later.

open as a page

Give cases where Kotlin refuses to smart-cast after an `is`/null check, and how you work around each.

level: middleimportance: must knowfreq 70%

basics

~20 s

Kotlin won't smart-cast when the value could change between check and use: mutable var properties, properties with custom getters, open properties, and values from other modules. Copy the value into a local val (or use ?.let) and check that instead.

open as a page

Why is Nothing a subtype of every type, and what does that enable in everyday Kotlin code?

level: middleimportance: must knowfreq 60%

basics

~20 s

Nothing sits at the bottom of Kotlin's type hierarchy, so it counts as every type at once. That lets a throw or a never-returning call be used wherever any value is expected, like on the right of the elvis operator.

open as a page

When a Kotlin `if/else` (or `when`) expression has one branch returning String and another returning null, what is the inferred type of the whole expression, and what general rule produces it?

level: middleimportance: must knowfreq 65%

basics

~10 s

The expression's type is String?. Kotlin takes the common supertype of all branches. One branch is String, the other is null (type Nothing?), and the smallest type covering both is String?.

open as a page

Contrast Unit with Nothing. When does a function return Unit versus Nothing, and how does each affect control flow?

level: middleimportance: must knowfreq 55%

basics

~10 s

Unit means the function returns normally but with no useful value. Nothing means the function never returns at all -- it always throws or loops forever. Unit has one value; Nothing has none.

open as a page

How does `kotlin.Any` differ from `java.lang.Object`? What happens to the extra `Object` methods when running on the JVM?

level: middleimportance: should knowfreq 55%

basics

~10 s

Any is Kotlin's root and only promises equals, hashCode, toString. Object has extra methods like wait, notify, getClass. On the JVM Any maps to Object, so those extras still exist but Kotlin hides them.

open as a page

Show idiomatic ways to use `as?` to avoid ClassCastException, and explain how it interacts with smart casts and the Elvis operator.

level: middleimportance: should knowfreq 50%

basics

~20 s

Use as? to try a cast and get null if it does not fit, then handle the null with the ?: operator to provide a default or return early. This avoids crashes from a bad cast.

open as a page

How do `is`-checks behave inside a `when` expression, and how does that interact with `sealed` exhaustiveness?

level: middleimportance: should knowfreq 65%

basics

~20 s

In when, each is Type -> branch smart-casts the subject to that type inside the branch. With a sealed class or enum subject used as an expression, the compiler can require all subtypes to be covered, so no else branch is needed.

open as a page

How do you declare a function that never returns, and what compiler behaviors does a Nothing return type unlock (TODO, error, require)?

level: middleimportance: should knowfreq 55%

basics

~20 s

Give the function the return type Nothing and make sure every path throws or loops forever. Then the compiler knows code after a call to it is dead and can smart-cast variables, the way TODO() and error() do.

open as a page

Given `listOf("a", null, "b")` and `listOf(1, "x")`, what element types does Kotlin infer, and how does nullable unification combine with generic variance to produce them?

level: middleimportance: should knowfreq 45%

basics

~20 s

listOf("a", null, "b") is List<String?> because the elements unify to String?. listOf(1, "x") is List<Any> because Int and String share only the supertype Any (or its intersection). The common type of all elements becomes the element type.

open as a page

How does Kotlin's Unit differ from Java's void, both conceptually and at the bytecode level?

level: middleimportance: should knowfreq 60%

basics

~20 s

Java's void is not a type and has no value. Kotlin's Unit is a real type with one value, so it can be used in generics. On the JVM, Unit-returning functions usually still compile to void methods.

open as a page

What is the difference between a generic parameter `<T>` and `<T : Any>`? When would you use the `Any` upper bound?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A plain <T> actually means <T : Any?>, so T may be nullable. Writing <T : Any> forces T to be a non-null type, banning String? and friends — useful when null would break your logic.

open as a page

What is the runtime difference between `null as String` and `null as String?`, and how do platform types from Java affect cast safety?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Casting null to a type that forbids null fails, but casting null to a nullable type is fine. Values coming from Java have an unknown null status, so a careless cast can crash later when a hidden null appears.

open as a page

Why does `x is List<String>` not compile, and what is the correct way to type-check a generic at runtime?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Generic type arguments are erased at runtime, so the JVM can't tell a List<String> from a List<Int>. Kotlin forbids is List<String>. Use a star projection is List<*> to check only that it's a list, then verify elements individually if needed.

open as a page

Explain how `is` interacts with nullable types and how negative `is`/null checks produce smart casts via control flow.

level: seniorimportance: should knowfreq 50%

basics

~20 s

x is String is true only for non-null Strings; for a nullable you can write is String? to include null. Kotlin also smart-casts via control flow: after if (x !is String) return, the rest of the function treats x as String, because the only way past is when the check held.

open as a page

What is the type of the literal `null`, and how do Nothing and Nothing? behave with generics like emptyList()?

level: seniorimportance: should knowfreq 45%

basics

~10 s

The bare null literal has type Nothing?, the nullable bottom type whose only value is null. Because Nothing is below everything, an empty list typed List<Nothing> can stand in for a list of anything.

open as a page

Explain where Nothing and Nothing? sit in Kotlin's type lattice, and how that bottom position interacts with nullable types during type unification.

level: seniorimportance: should knowfreq 50%

basics

~20 s

Nothing is the bottom type: it is a subtype of every type and has no values. Nothing? is its nullable form, sitting just above Nothing, and is a subtype of every nullable type T?. Together they anchor the bottom of the lattice.

open as a page

Explain Unit's role in lambdas and SAM/functional-type conversions: the last-expression coercion to Unit and why () -> Unit lambdas don't need an explicit return.

level: seniorimportance: should knowfreq 45%

basics

~20 s

When a lambda's expected type returns Unit, Kotlin ignores whatever the last expression produces and treats the result as Unit. So you never need to add return Unit or worry that the last line returns something else.

open as a page

When designing a public type, what subtle pitfalls arise around the `Any` members `equals`, `hashCode`, and `toString` — especially for sensitive data and arrays?

level: seniorimportance: nice to knowfreq 35%

basics

~10 s

Default toString leaks little, but data class toString prints every field — including secrets. Arrays compare by identity, not content. And mutable fields used in hashCode corrupt hash-map keys. Override these members carefully.

open as a page

How does the compiler use Nothing-returning expressions in control-flow / smart-cast analysis, and where can this break down?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

When a branch ends in something that never returns, the compiler knows execution can't continue down that path, so afterward it can treat variables as non-null or narrowed. This breaks if the variable could change between the check and use.

open as a page

How does the nullable lattice handle generic type parameters whose bound is non-null (e.g. `<T : Any>` vs `<T>`), and what happens to nullability when a type parameter T is itself nullable and you write T? — is there a 'double nullable' T??

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

An unbounded T defaults to bound Any?, so T can already be nullable. Writing T? just guarantees nullability. There is no T?? — nullable is idempotent: making an already-nullable type nullable again changes nothing.

open as a page

You must safely cast an erased `Any` (e.g., a deserialized value) into a generic type like `List<User>`. How do you make this robust given type erasure, and when is `@Suppress("UNCHECKED_CAST")` justified?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Because the program forgets the inside type of a list at runtime, you cannot fully trust one big cast. Check each element instead, or use a tool that keeps the type, and only silence the warning when something else guarantees the contents are correct.

open as a page

When designing generic APIs and Java interop, when should you pick Unit as a type argument, and what pitfalls arise (e.g. Unit vs Void, suspend functions, Continuation)?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Use Unit as a generic argument when an API needs a type but there's no meaningful payload, like a result that only signals success. Watch out for Java interop where Unit maps to kotlin.Unit, not java.lang.Void, and for suspend functions whose Unit result still flows through Continuation.

open as a page