How do I use inline functions and reified types in collection processing?

In Kotlin, inline functions and reified type parameters are especially useful in collection processing when you want to write generic, type-safe utilities that still need access to the actual runtime type.

Normally, generic type information is erased at runtime, but reified type parameters in inline functions let you do checks like is T, as T, or filterIsInstance<T>().

Basic idea

inline fun <reified T> Iterable<*>.onlyOfType(): List<T> {
    return this.filterIsInstance<T>()
}

Usage:

val items: List<Any> = listOf("Kotlin", 42, "Java", 3.14)

val strings = items.onlyOfType<String>()

println(strings) // [Kotlin, Java]

Because T is reified, Kotlin knows at runtime that T is String.

Why inline is required

A reified type parameter is only allowed on an inline function:

inline fun <reified T> someFunction(value: Any): Boolean {
    return value is T
}

This works because the compiler substitutes the function body at the call site and preserves the concrete type.

This would not compile:

fun <T> someFunction(value: Any): Boolean {
    return value is T // Error: cannot check for instance of erased type
}

Filtering a collection by type

inline fun <reified T> List<*>.filterByType(): List<T> {
    return filterIsInstance<T>()
}

Example:

val mixed = listOf(1, "two", 3L, "four", 5.0)

val strings = mixed.filterByType<String>()
val ints = mixed.filterByType<Int>()

println(strings) // [two, four]
println(ints)    // [1]

Mapping only matching elements

You can combine reified type checks with mapNotNull:

inline fun <reified T, R> Iterable<*>.mapIfType(
    transform: (T) -> R
): List<R> {
    return mapNotNull { item ->
        if (item is T) transform(item) else null
    }
}

Usage:

val values: List<Any> = listOf("one", 2, "three", 4)

val lengths = values.mapIfType<String> { it.length }

println(lengths) // [3, 5]

Finding the first item of a type

inline fun <reified T> Iterable<*>.firstOfTypeOrNull(): T? {
    return firstOrNull { it is T } as? T
}

Usage:

val items: List<Any> = listOf(10, "hello", 20)

val firstString = items.firstOfTypeOrNull<String>()

println(firstString) // hello

A slightly cleaner version uses filterIsInstance:

inline fun <reified T> Iterable<*>.firstOfTypeOrNull(): T? {
    return filterIsInstance<T>().firstOrNull()
}

Grouping elements by runtime type

inline fun <reified T> Iterable<*>.partitionByType(): Pair<List<T>, List<Any?>> {
    val matching = mutableListOf<T>()
    val others = mutableListOf<Any?>()

    for (item in this) {
        if (item is T) {
            matching += item
        } else {
            others += item
        }
    }

    return matching to others
}

Usage:

val data = listOf("a", 1, "b", 2.0, null)

val result = data.partitionByType<String>()

println(result.first)  // [a, b]
println(result.second) // [1, 2.0, null]

Processing collections with inline lambdas

inline is also useful for performance when you pass lambdas to collection-like helper functions. It avoids allocating a function object in many cases.

inline fun <T, R> Iterable<T>.transformEach(
    transform: (T) -> R
): List<R> {
    val result = ArrayList<R>()

    for (item in this) {
        result += transform(item)
    }

    return result
}

Usage:

val numbers = listOf(1, 2, 3)

val doubled = numbers.transformEach { it * 2 }

println(doubled) // [2, 4, 6]

Combining inline and reified

A common pattern is a type-safe processing function:

inline fun <reified T, R> Iterable<*>.processType(
    transform: (T) -> R
): List<R> {
    return mapNotNull { item ->
        if (item is T) {
            transform(item)
        } else {
            null
        }
    }
}

Example:

val events: List<Any> = listOf(
    "login",
    404,
    "logout",
    500
)

val uppercasedEvents = events.processType<String> {
    it.uppercase()
}

println(uppercasedEvents) // [LOGIN, LOGOUT]

Example with sealed classes

sealed interface Event

data class ClickEvent(val x: Int, val y: Int) : Event
data class TextEvent(val text: String) : Event
data class ErrorEvent(val message: String) : Event

inline fun <reified T : Event> Iterable<Event>.ofEventType(): List<T> {
    return filterIsInstance<T>()
}

Usage:

val events: List<Event> = listOf(
    ClickEvent(10, 20),
    TextEvent("hello"),
    ErrorEvent("failed"),
    TextEvent("world")
)

val textEvents = events.ofEventType<TextEvent>()

println(textEvents.map { it.text }) // [hello, world]

Important limitations

1. reified only works in inline functions

inline fun <reified T> ok(value: Any) = value is T

But this does not work:

fun <T> notOk(value: Any) = value is T

2. It does not fully solve nested generic type erasure

This can be misleading:

val data: List<Any> = listOf(listOf("a"), listOf(1))

val stringLists = data.filterIsInstance<List<String>>()

Because of type erasure, the runtime can check that each item is a List, but not that each list contains String.

A safer approach:

val stringLists = data
    .filterIsInstance<List<*>>()
    .filter { list -> list.all { it is String } }
    .map { list -> list.map { it as String } }

Practical collection utilities

inline fun <reified T> Iterable<*>.countOfType(): Int {
    return count { it is T }
}

inline fun <reified T> Iterable<*>.containsType(): Boolean {
    return any { it is T }
}

inline fun <reified T> Iterable<*>.withoutType(): List<Any?> {
    return filterNot { it is T }
}

Usage:

val values = listOf("a", 1, "b", 2.0)

println(values.countOfType<String>())   // 2
println(values.containsType<Double>())  // true
println(values.withoutType<String>())   // [1, 2.0]

Rule of thumb

Use inline + reified when:

  • You need to check item is T
  • You need to cast with as T or as? T
  • You want to call APIs like filterIsInstance<T>()
  • You want type-safe helpers for heterogeneous collections
  • You want to avoid passing KClass<T> or Class<T> manually

For ordinary collection transformations where no runtime type check is needed, a regular generic function is usually enough:

fun <T, R> Iterable<T>.mapCustom(transform: (T) -> R): List<R> {
    return map(transform)
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.