
This is a chapter from the book Kotlin Essentials. You can find it on LeanPub or Amazon. It is also available as a course.
surname is String, the type of age is Int, and we can use return and throw on the right side of the Elvis operator.fun processPerson(person: Person?) { val name = person?.name ?: "unknown" val surname = person?.surname ?: return val age = person?.age ?: throw Error("Person must have age") // ... }
User used four times. Can you tell me which usages are classes, which are types, and which are something else?
class keyword, you define a class name. A class is a template for objects that defines a set of properties and methods. When we call a constructor, we create an object. Types are used here to specify what kind of objects we expect to have in the variables[^20_1].

User. It generates two types. Can you name them both? One is User, but the second is not Any (Any is already in the type hierarchy). The second new type generated by the class User is User?. Yes, the nullable variant is a separate type.Box<T> class theoretically generates an infinite number of types.
User is a type. Only in positions that represent types can you use User? instead of User.
sum function,, which is an extension of Iterable<Int>, or the isNullOrBlank function, which is an extension of String?.fun Iterable<Int>.sum(): Int { var sum: Int = 0 for (element in this) { sum += element } return sum } @OptIn(ExperimentalContracts::class) inline fun CharSequence?.isNullOrBlank(): Boolean { // (skipped contract definition) return this == null || this.isBlank() }
Dog and its superclass Animal.open class Animal class Dog : Animal()
Animal type is expected, you can use a Dog, but not the other way around.fun petAnimal(animal: Animal) {} fun petDog(dog: Dog) {} fun main() { val dog: Dog = Dog() val dogAnimal: Animal = dog // works petAnimal(dog) // works val animal: Animal = Animal() val animalDog: Dog = animal // compilation error petDog(animal) // compilation error }
Dog is a subtype of Animal. By rule, when A is a subtype of B, we can use A where B is expected. We might also say that Animal is a supertype of Dog, and a subtype can be used where a supertype is expected.
fun petDogIfPresent(dog: Dog?) {} fun petDog(dog: Dog) {} fun main() { val dog: Dog = Dog() val dogNullable: Dog? = dog petDogIfPresent(dog) // works petDogIfPresent(dogNullable) // works petDog(dog) // works petDog(dogNullable) // compilation error }

Any, which is similar to Object in Java. The supertype of all the types is not Any, it is Any?. Any is a supertype of all non-nullable types. We also have something that is not present in Java and most other mainstream languages: the subtype of all the types, which is called Nothing. We will talk about it soon.
Any is only a supertype of non-nullable types. So, wherever Any is expected, nullable types will not be accepted. This fact is also used to set a type parameter’s upper boundary to accept only non-nullable types[^20_5].fun <T : Any> String.parseJson(): T = ...
Unit does not have any special place in the type hierarchy. It is just an object declaration that is used when a function does not specify a result type.object Unit { override fun toString() = "kotlin.Unit" }
Nothing.Nothing is a subtype of all the types in Kotlin. If we had an instance of this type, it could be used instead of everything else (like a Joker in the card game Rummy). It’s no wonder that such an instance does not exist. Nothing is an empty type (also known as a bottom type, zero type, uninhabited type, or never type), which means it has no values. It is literally impossible to make an instance of type Nothing, but this type is still really useful. I will tell you more: some functions declare Nothing as their result type. You've likely used such functions many times already. What functions are those? They declare Nothing as a result type, but they cannot return it because this type has no instances. But what can these functions do? Three things: they either need to run forever, end the program, or throw an exception. In all cases, they never return, so the Nothing type is not only valid but also really useful.fun runForever(): Nothing { while (true) { // no-op } } fun endProgram(): Nothing { exitProcess(0) } fun fail(): Nothing { throw Error("Some error") }
TODO()? This function throws a NotImplementedError exception. There is also the error function from the standard library, which throws an IllegalStateException.inline fun TODO(): Nothing = throw NotImplementedError() inline fun error(message: Any): Nothing = throw IllegalStateException(message.toString())
TODO is used as a placeholder in a place where we plan to implement some code.fun fib(n: Int): Int = TODO()
error is used to signal an illegal situation:fun get(): T = when { left != null -> left right != null -> right else -> error("Must have either left or right") }
Int or Nothing. What should the inferred type be? The closest supertype of both Int and Nothing is Int. This is why the inferred type will be Int.// the inferred type of answer is Int val answer = if (timeHasPassed) 42 else TODO()
name and fullName is String because both fail and error declare Nothing as their result type. This is a huge convenience.fun processPerson(person: Person?) { // the inferred type of name is String val name = person?.name ?: fail() // the inferred type of fullName is String val fullName = when { !person.middleName.isNullOrBlank() -> "$name ${person.middleName} ${person.surname}" !person.surname.isNullOrBlank() -> "$name ${person.surname}" else -> error("Person must have a surname") } // ... }
return or throw on the right side of a variable assignment?fun main() { val a = return val b = throw Error() }
return and throw end the function, so we will never assign anything to such variables (like a and b in the example above). This assignment is an unreachable piece of code. In Kotlin, it just causes a warning.
return and throw are expressions, which means they declare a result type. This type is Nothing.fun main() { val a: Nothing = return val b: Nothing = throw Error() }
return or throw on the right side of the Elvis operator or in a when-expression.fun processPerson(person: Person?) { val name = person?.name ?: return val fullName = when { !person.middleName.isNullOrBlank() -> "$name ${person.middleName} ${person.surname}" !person.surname.isNullOrBlank() -> "$name ${person.surname}" else -> return } // ... }
fun processPerson(person: Person?) { val name = person?.name ?: throw Error("Name is required") val fullName = when { !person.middleName.isNullOrBlank() -> "$name ${person.middleName} ${person.surname}" !person.surname.isNullOrBlank() -> "$name ${person.surname}" else -> throw Error("Surname is required") } // ... }
return and throw declare Nothing as their result type. As a consequence, Kotlin will infer String as the type of both name and fullName because String is the closest supertype of both String and Nothing.Nothing. Just like John Snow.
Nothing as a return type, it means that everything after its call is not reachable. This is reasonable: there are no instances of Nothing, so it cannot be returned. This means a statement that declares Nothing as its result type will never complete in a normal way, so the next statements are not reachable. This is why everything after either fail or throw will be unreachable.
return, TODO, error, etc. If a non-optional expression declares Nothing as its result type, everything after that is unreachable. This is a simple rule, but it’s useful for the compiler. It’s also useful for us since it gives us more possibilities. Thanks to this rule, we can use TODO() in a function instead of returning a value. Anything that declares Nothing as a result type ends the function (or runs forever), so this function will not end without returning or throwing first.fun fizzBuzz(): String { TODO() }
MutableStateFlow class, which represents a mutable value whose state changes can be observed using the collect method. The thing is that collect suspends the current coroutine until whatever it observes is closed, but a StateFlow cannot be closed. This is why this collect function declares Nothing as its result type.public interface SharedFlow<out T> : Flow<T> { public val replayCache: List<T> override suspend fun collect( collector: FlowCollector<T> ): Nothing }
collect works. Thanks to the result type, IntelliJ informs them that the code they place after collect is unreachable.
SharedFlow cannot be closed, so its
collect function will never return, therefore it declares Nothing as its result type.null to a variable without setting an explicit type? What’s more, such a variable can be used wherever null is accepted.fun main() { val n = null val i: Int? = n val d: Double? = n val str: String? = n }
null has its type, which is a subtype of all nullable types. Take a look at the type hierarchy and guess what type this is.
null is Nothing?. Now think about the inferred type of a and b in the example below.val a = if (predicate) "A" else null val b = when { predicate2 -> "B" predicate3 -> "C" else -> null }
String and Nothing? is String?. The same is true about the when-expression: the closest supertype of String, String, and Nothing? is String?. Everything makes sense.String?, we can pass either String or null, whose type is Nothing?. This is clear when you take a look at the type hierarchy. String and Nothing? are the only non-empty subtypes of String?.
- A class is a template for creating objects. A type defines expectations and functionalities.
- Every class generates a nullable and a non-nullable type.
- A nullable type is a supertype of the non-nullable variant of this type.
- The supertype of all types is
Any?. - The supertype of non-nullable types is
Any. - The subtype of all types is
Nothing. - When a function declares
Nothingas a return type, this means that it will throw an error or run infinitely. - Both
throwandreturndeclareNothingas their result type. - The Kotlin compiler understands that when an expression declares
Nothingas a result type, everything after that is unreachable. - The type of
nullisNothing?, which is the subtype of all nullable types.
[^20_2]: Except when figuring out which function to choose in the case of overloading.
[^20_3]: It all depends on what we measure, but Python, Java, and JavaScript take the first three positions in most rankings. In some, they are beaten by C, which is widely used for very low-level development, like developing processors for cars or refrigerators.
[^20_4]: Type arguments and type parameters will be better explained in the chapter Generics.
[^20_5]: I will explain type parameters' upper boundaries in the chapter Generics.
[^20_6]: Formally, JavaScript supports weak typing, but in this chapter we discuss static typing, which is not supported by JavaScript.
