← All courses

Generics

🗓 May 31, 2026 ⏱ 2 min read

Why generics?

Imagine writing a “box” that can hold a value. Without generics you’d write one box for Int, another for String, and so on. Generics let you write it once and use it with any type, while keeping full type safety — the compiler still knows exactly what’s inside.

class Box<T>(val value: T)

val intBox = Box(42)          // Box<Int>
val strBox = Box("hello")     // Box<String>
println(intBox.value + 1)     // compiler knows it's an Int

Generic functions

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

val n = firstOrNull(listOf(1, 2, 3))   // Int?
val s = firstOrNull(listOf("a", "b"))  // String?

Constraints: limiting the type

Sometimes a type must have certain abilities. A constraint says “T must be a kind of X”:

fun <T : Comparable<T>> maxOf(a: T, b: T): T =
    if (a > b) a else b      // works because T can be compared

maxOf(3, 9)          // 9
maxOf("apple", "kiwi") // kiwi

Generic classes in real life

// a reusable result wrapper used across many apps
sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val message: String) : ApiResult<Nothing>()
}

fun handle(result: ApiResult<User>) = when (result) {
    is ApiResult.Success -> show(result.data)   // data is a User
    is ApiResult.Error   -> showError(result.message)
}

in and out (a gentle intro)

You’ll sometimes see out T (the type is only produced/returned, like a read-only list) and in T (the type is only consumed/passed in). These “variance” keywords let generic types be used more flexibly and safely. As a beginner, just know List<out T> is why a List<Dog> can be used where a List<Animal> is expected.

Common mistakes

  • Reaching for Any and casting everywhere instead of using a generic type.
  • Forgetting constraints — if you need to compare or add, constrain the type.
  • Over-engineering — only make something generic when it’s genuinely reused.
Summary: Generics (<T>) let you write one reusable, type-safe piece of code for any type. Add constraints (T : Comparable<T>) when the type needs certain abilities.