skip to content

How does the standard-library function `filterIsInstance<T>()` work, and why must it be `inline` with a `reified` parameter? Show how it differs from a manual `filter { it is T }`.

level: middleimportance: should knowfreq 40%

answer

  1. filterIsInstance<T>() = filter by type + narrow result to List<T>
  2. inline + reified makes `element is T` legal
  3. Plain filter { it is T } won't compile (erased T)
  4. Class<T> overload for Java interop / non-reified
  5. filterIsInstance<List<String>>() only checks is List (erasure)

basics

~20 s

filterIsInstance<T>() keeps only elements that are of type T and returns them already typed as T. It must be inline + reified so the is T check works at runtime; a plain filter { it is T } can't even compile because T would be erased.

solid answer

~50 s

`filterIsInstance<T>()` is declared roughly as `inline fun <reified T> Iterable<*>.filterIsInstance(): List<T>`. Because `T` is reified, the compiler inlines the body and substitutes the concrete type, so the internal `if (element is T)` check is legal and the result is a `List<T>` — already narrowed, no extra cast needed. You cannot write the equivalent inside a normal generic helper: `fun <T> List<Any>.onlyT() = filter { it is T }` fails to compile because `T` is erased and `it is T` is rejected. The reified version succeeds precisely because it is inlined into the call site where the real type is known. There is also an overload `filterIsInstance(klass: Class<T>)` taking an explicit `Class` for cases (like Java interop or non-reified contexts) where you have a `Class` object instead of a call-site type. Watch the erasure caveat: `filterIsInstance<List<String>>()` actually only checks `is List`, since the element type is itself erased.

code

kotlin · 6 lines
kotlin
val items: List<Any> = listOf(1, "a", 2, null, "b")
val onlyStrings: List<String> = items.filterIsInstance<String>() // [a, b]

// Equivalent reified helper you could write yourself:
inline fun <reified T> Iterable<*>.keepType(): List<T> =
    this.mapNotNull { it as? T }

go deeper

for a junior

Knows filterIsInstance<T>() returns only elements of type T already typed as T.

for a middle

Explains the inline+reified implementation and why a plain filter { it is T } won't compile.

for a senior

Articulates the erasure gotcha (filterIsInstance<List<String>>() only checks is List) and the Class overload.

for a principal

Reasons about API design: when to expose a reified vs Class-based overload, inlining cost, and nullable-type edge cases.

## What it does `filterIsInstance<T>()` walks a collection and returns a new `List<T>` containing only the elements whose runtime type is `T` — and the result is already typed as `T`, so you don't cast. ```kotlin val mixed: List<Any> = listOf(1, "two", 3, "four", 5.0) val ints: List<Int> = mixed.filterIsInstance<Int>() // [1, 3] val strs: List<String> = mixed.filterIsInstance<String>() // [two, four] ``` ## Roughly how it's implemented ```kotlin public inline fun <reified T> Iterable<*>.filterIsInstance(): List<T> { val out = ArrayList<T>() for (element in this) if (element is T) out.add(element) return out } ``` The key line is `element is T`. That is only legal because: 1. the function is `inline` — its body is copied into the call site, and 2. `T` is `reified` — the compiler substitutes the concrete type there. So `mixed.filterIsInstance<Int>()` becomes, at the call site, `if (element is Int) ...`. ## Why a plain `filter { it is T }` fails ```kotlin fun <T> Iterable<*>.onlyT(): List<T> = filter { it is T } // COMPILE ERROR: Cannot check for instance of erased type: T .map { it as T } ``` In a normal (non-inline) generic function `T` is erased; you literally cannot write `it is T`. `filterIsInstance` sidesteps this with `inline` + `reified`. ## The explicit-Class overload There is also `fun <R> Iterable<*>.filterIsInstance(klass: Class<R>): List<R>`. It takes a `Class` object instead of a reified type — useful from Java or when you only have a `Class<R>` at hand. ## Erasure gotcha Reification gives you the *class*, not its generic arguments. So: ```kotlin listOf(listOf(1), listOf("a")).filterIsInstance<List<String>>() ``` actually checks `is List` for each element and returns **both** lists — the `<String>` is unenforceable because element generics are erased. `typeOf` doesn't help here because the runtime values themselves carry no element-type tag.

  • Does `filterIsInstance<String?>()` keep nulls?
    Yes — `null is String?` is true, so nullable-typed elements including null are retained. With `filterIsInstance<String>()` (non-nullable) nulls are dropped because `null is String` is false.
  • Why is there also a `filterIsInstance(klass: Class<T>)` overload?
    For contexts where you have a runtime `Class` object rather than a call-site type — e.g. Java interop, or a non-inline function that received a `Class<T>` parameter and cannot use reified.

saying these in an interview costs you the question

  • Saying filter { it is T } works in any generic function
  • Believing filterIsInstance<List<String>>() filters by element type
  • Thinking the result still needs a manual cast to List<T>
  • Not knowing reified is what makes the is-check legal
  • Confusing it with filterNotNull

context