Generics Workbook


Practice problems for Kotlin Generics: Variance Without the Wildcards. Each takes a minute or two. Write your own answer first, then click Show answer — nothing here is a trick question, just direct practice of the syntax from the lesson.

the basics

1. A generic function

Write firstOrNull that takes a List of any element type and returns its first element, or null when empty.

Show answer Hide answer
fun <T> firstOrNull(list: List<T>): T? =
    if (list.isEmpty()) null else list[0]

2. A generic class

Declare a Box that holds one value of any type.

Show answer Hide answer
class Box<T>(val value: T)

3. Inference at the call site

Given the Box above, create one holding "hello". Do you write the type argument?

Show answer Hide answer
val b = Box("hello")   // T inferred as String

No — the compiler infers T from the argument.

variance

4. A producer

Declare an interface Source that only ever produces a T (a next(): T method), marked so that a Source of Dog is usable as a Source of Animal.

Show answer Hide answer
interface Source<out T> {
    fun next(): T
}

out makes it covariant — Kotlin’s declaration-site version of Java’s ? extends.

5. A consumer

Declare an interface Sink that only ever consumes a T (an accept(value: T) method), marked so that a Sink of Animal is usable as a Sink of Dog.

Show answer Hide answer
interface Sink<in T> {
    fun accept(value: T)
}

in makes it contravariant — Kotlin’s version of ? super.

6. Covariant by declaration

Declare an interface Producer whose only method returns a T, marked so a Producer of String is usable where a Producer of Any is expected.

Show answer Hide answer
interface Producer<out T> {
    fun produce(): T
}

val p: Producer<Any> = object : Producer<String> {
    override fun produce() = "hi"
}

out (covariance) is allowed because T only ever comes out.

projections and reified

7. Don’t care about the argument

Write a function size that accepts a List of any unknown element type and returns its size.

Show answer Hide answer
fun size(items: List<*>) = items.size

* is the star projection — Kotlin’s equivalent of Java’s bare ?.

8. Keep the type at runtime

Write an extension asOrNull that safely casts any receiver to the requested type T, returning null on a mismatch — using reified.

Show answer Hide answer
inline fun <reified T> Any.asOrNull(): T? = this as? T

val name = value.asOrNull<String>()

reified (only on inline functions) keeps T available at runtime, defeating erasure.

9. First element of a type

Write firstOfType — an inline extension on Iterable<*> with a reified T — returning the first element that is a T, or null.

Show answer Hide answer
inline fun <reified T> Iterable<*>.firstOfType(): T? =
    firstOrNull { it is T } as T?

reified keeps T at runtime so it is T compiles — possible only because the function is inline.


Back to the lesson, Kotlin Generics, or on to the next one: exceptions.