0Pricing
Kotlin Academy · Lesson

Generic Functions and Type Constraints with where

Write generic functions with single and multiple type constraints.

Generic Functions and Type Constraints with where is a free Kotlin Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Generic Function Basics

Generic functions use type parameters in angle brackets before the function name. The type parameter can be used in parameter types and return types.

fun <T> identity(value: T): T = value
fun <T> listOf2(a: T, b: T): List<T> = listOf(a, b)
fun main() {
    println(identity(42))       // 42
    println(identity("hello"))  // hello
    println(listOf2(1, 2))      // [1, 2]
}

Upper Bounds with : Constraint

Constrain a type parameter with an upper bound using :. The type must be a subtype of the bound.

fun <T : Comparable<T>> max(a: T, b: T): T = if (a >= b) a else b
fun main() {
    println(max(3, 7))          // 7
    println(max("apple", "banana")) // banana
    // max(listOf(1), listOf(2)) // error: List is not Comparable
}

Nullable Upper Bound

The default upper bound is Any? (nullable). Use T : Any to exclude null.

fun <T : Any> notNull(value: T?): T {
    return value ?: throw IllegalArgumentException("Value is null")
}
fun main() {
    println(notNull("hello"))  // hello
    try { notNull(null) } catch (e: Exception) { println(e.message) }
}

Multiple Constraints with where

Use the where clause to apply multiple constraints to a single type parameter.

fun <T> process(item: T): String
        where T : Comparable<T>, T : CharSequence {
    return "Length=${item.length}, sorted=${item > "a"}"
}
fun main() {
    println(process("kotlin")) // Length=6, sorted=true
    // process(42) // error: Int doesn't implement CharSequence
}

where on a Class

The where clause also works on class-level type parameters.

class SortedContainer<T>(private val items: MutableList<T> = mutableListOf())
        where T : Comparable<T>, T : Any {
    fun add(item: T) { items.add(item); items.sort() }
    fun top(): T? = items.lastOrNull()
}
fun main() {
    val c = SortedContainer<Int>()
    c.add(5); c.add(2); c.add(8)
    println(c.top()) // 8
}

Generic Extension Functions

Extension functions can also have type parameters, making them available on specific generic types.

fun <T : Comparable<T>> List<T>.second(): T? {
    return if (size >= 2) this[1] else null
}
fun main() {
    println(listOf(10, 20, 30).second()) // 20
    println(listOf("a").second())        // null
}

Type Parameter in Return Position

Generic functions can infer the type from context, avoiding explicit type arguments at the call site.

fun <T> MutableList<T>.popOrDefault(default: T): T {
    return if (isEmpty()) default else removeAt(lastIndex)
}
fun main() {
    val list = mutableListOf(1, 2, 3)
    println(list.popOrDefault(0)) // 3
    println(list.popOrDefault(0)) // 2
    println(mutableListOf<Int>().popOrDefault(-1)) // -1
}

Reusable Generic Utilities

Generic functions make utility functions reusable across types without duplication.

fun <T> Iterable<T>.firstOrElse(default: T): T = firstOrNull() ?: default
fun <T, R : Comparable<R>> Iterable<T>.maxByOrElse(selector: (T) -> R, default: T): T =
    maxByOrNull(selector) ?: default
fun main() {
    println(listOf<String>().firstOrElse("fallback")) // fallback
    println(listOf("a","bbb","cc").maxByOrElse({ it.length }, "")) // bbb
}

Star Projection vs Bounded Wildcard

Contrast using a bounded type parameter (useful when you need to produce or consume T) with star projection (when the type is unknown).

fun <T : Number> sumList(list: List<T>): Double =
    list.sumOf { it.toDouble() }
// Star projection: read-only, type unknown
fun printAll(list: List<*>) = list.forEach { println(it) }
fun main() {
    println(sumList(listOf(1, 2, 3)))    // 6.0
    printAll(listOf("a", 1, true))       // a, 1, true
}

Generic Pair Swap

A simple but illustrative generic function that reverses the components of a Pair.

fun <A, B> Pair<A, B>.swap(): Pair<B, A> = Pair(second, first)
fun main() {
    val pair = Pair("hello", 42)
    val swapped = pair.swap()
    println(swapped) // (42, hello)
}

Practical: Generic Result Unwrapper

Use bounded generics to write a utility that maps over a Result<T> with a typed transformation.

fun <T : Any, R : Any> Result<T>.mapNotNull(transform: (T) -> R?): Result<R> {
    return fold(
        onSuccess = { v ->
            val r = transform(v)
            if (r != null) Result.success(r) else Result.failure(NoSuchElementException())
        },
        onFailure = { Result.failure(it) }
    )
}
fun main() {
    val r = Result.success("123")
    val n = r.mapNotNull { it.toIntOrNull() }
    println(n) // Success(123)
}

Quick Check

How do you apply multiple constraints to one type parameter?

Recap

Generic functions use type parameters to write reusable, type-safe code. Constrain with : for a single bound or where for multiple bounds. The Kotlin compiler enforces constraints at the call site.

Frequently asked questions

Is the “Generic Functions and Type Constraints with where” lesson free?

Yes — the full text of “Generic Functions and Type Constraints with where” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.

What will I learn in “Generic Functions and Type Constraints with where”?

Write generic functions with single and multiple type constraints. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Kotlin Academy?

No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Generic Functions and Type Constraints with where” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Kotlin Academy lesson?

Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Generic Functions and Type Constraints with where
  2. Declaration-Site Variance: in and out
  3. Star Projection and When to Use *
  4. Type Erasure and reified Type Parameters
← Back to Kotlin Academy