What does filterIsInstance do, and how does it differ from filter { it is T } plus a manual cast? Why does it need a reified type?
answer
- keeps elements of type R, returns List<R> (no cast)
- inline + reified so the is-check works at runtime
- JVM erases generics; reified restores R at the call site
- filter { it is T } leaves List<Any>, needs extra cast
- drops nulls; Class<R> overload + filterIsInstanceTo exist
basics
~20 sfilterIsInstance keeps only the elements that are of a given type and gives you back a list already typed as that type, so you do not have to cast each one yourself. It needs the type known at compile time to do this safely.
solid answer
~40 sfilterIsInstance<R>() returns a List<R> containing only the receiver elements that are instances of R, with the result already narrowed to R — no manual cast needed. It is implemented as an inline function with a reified type parameter, so R is available at runtime to do the is check; without reified, generic type arguments are erased on the JVM and the runtime is-check would be impossible. Compared to filter { it is String }.map { it as String }, filterIsInstance does it in one operator, avoids the redundant cast (smart-cast can't carry across operators), and reads clearer. There's also a filterIsInstance(klass: Class<R>) overload taking a Class token (useful when the type isn't statically known) and filterIsInstanceTo(destination) for writing into an existing collection. Note nullability: filterIsInstance<String>() drops nulls since null isn't an instance of String.
code
kotlin · 6 linessealed interface Event
data class Click(val x: Int) : Event
data class Key(val code: Int) : Event
val events: List<Event> = listOf(Click(1), Key(65), Click(2))
val clicks: List<Click> = events.filterIsInstance<Click>() // [Click(1), Click(2)]go deeper
Knows filterIsInstance keeps elements of a type and returns them already typed, avoiding casts.
Explains why it beats filter + manual cast and that it drops nulls.
Connects it to inline+reified and JVM type erasure, and knows the Class overload and filterIsInstanceTo variant.
Articulates the erasure limitation on nested generics, the unchecked-cast warning, and API trade-offs of reified vs Class-token designs.
## What it does `filterIsInstance<R>()` filters a collection down to only the elements that are instances of `R`, **and** returns the result already typed as `List<R>`. ```kotlin val mixed: List<Any> = listOf(1, "two", 3, "four", null) val strings: List<String> = mixed.filterIsInstance<String>() // ["two", "four"] ``` Note `null` is excluded — `null is String` is `false`. To keep nullable strings you'd use a nullable type argument `filterIsInstance<String?>()`. ## Why reified Its signature is roughly: ```kotlin public inline fun <reified R> Iterable<*>.filterIsInstance(): List<R> = filterIsInstanceTo(ArrayList<R>()) ``` - `inline` means the compiler copies the function body into the call site. - `reified` means the type argument `R` is **available at runtime** inside the body. On the JVM, generic type arguments are normally **erased** — at runtime a `List<String>` is just a `List`. A normal generic function therefore *cannot* do `x is R`, because `R` isn't known at runtime. Marking `R` as `reified` (only possible on `inline` functions) makes the compiler substitute the concrete type at each call site, so the `is R` check actually works. ## vs filter + manual cast ```kotlin // verbose, double work: val s1 = mixed.filter { it is String }.map { it as String } // idiomatic: val s2 = mixed.filterIsInstance<String>() ``` With `filter { it is String }` the result type stays `List<Any>` — the smart-cast inside the lambda doesn't propagate to the outer result, so you still need a `map { it as String }`. `filterIsInstance` collapses both steps and narrows the type for you in a single pass. ## Overloads and variants - `filterIsInstance(klass: Class<R>)` — non-reified overload taking a `Class` token; use when the type is only known at runtime (e.g. passed in dynamically). - `filterIsInstanceTo(destination)` — writes matching elements into an existing `MutableCollection`. ## Gotchas - **Erasure of the argument's own generics**: `filterIsInstance<List<String>>()` can only check that elements are a `List` (the inner `String` is erased), so it may admit a `List<Int>`. The compiler warns about this. - Nulls are dropped unless you ask for a nullable type.
- Why can't a normal (non-inline) generic function do `x is R`?Because JVM generics are erased: at runtime R is unknown. Only inline functions with a reified type parameter let the compiler inline the concrete type at the call site, making the is-check legal.
- What's the limitation of filterIsInstance<List<String>>()?Erasure means the runtime check can only confirm the element is a List; the String type argument is erased, so a List<Int> would also pass. The compiler warns this check is unchecked.
saying these in an interview costs you the question
- Saying the result is still List<Any> and needs casting
- Not knowing reified requires inline
- Claiming generic checks work without reification on the JVM
- Forgetting that filterIsInstance<String>() drops nulls
- Asserting filterIsInstance<List<String>>() fully checks the inner type