Demystifying Kotlin's is Check: How It Outsmarts Type Erasure

As a software engineer, you’ve likely heard the rule: on the JVM, generic types are erased at runtime. A List<String>
and a List<Int>
both become a raw List. This is why a check like myList is List<String>
inside a function that accepts List<Any>
results in a compile-time error. The compiler knows it can't make that guarantee at runtime. So, when you see a piece of Kotlin code like this, it should make you pause:
fun printCollectionContents(l: Collection<Int>) {
when(l) {
is List<Int> -> println("Integers in List: $l")
is Set<Int> -> println("Integers in Set: $l")
}
}
Wait a minute. If types are erased, how can this code possibly check for is List? Is this an exception to the rule?
The answer is a beautiful example of the Kotlin compiler's intelligence. Let's break down what’s really happening.
The Rule: Type Erasure is Real
First, let's confirm our baseline. Type erasure is very much a real phenomenon on the JVM. If you try to check the generic type of a collection whose elements aren't known at compile time, the compiler will stop you.Consider this function from the same file:
fun printListContents(l: List<Any>) {
when(l) {
// Compilation Error - Cannot check for instance of erased type 'List<String>'.
is List<String> -> println("Strings: $l")
// Compilation Error - Cannot check for instance of erased type 'List<Int>'.
is List<Int> -> println("Integers: $l")
}
}
Here, the compiler correctly identifies that it's impossible to differentiate a List from a List at runtime when all you know is that you have a List.
The "Magic": Compile-Time Guarantees
So why does printCollectionContents
work? The secret lies in its function signature:
fun printCollectionContents(l: Collection)
This signature provides a compile-time guarantee. The compiler enforces that whatever code calls this function must pass an object that conforms to Collection.
With this guarantee in place, the compiler can be clever about the when statement. The runtime check is List<Int>
is effectively simplified into two parts:
Is
l
an instance ofList
? This is a standard runtime check that the JVM can easily perform.Does it contain
Int
s? This part doesn't need to be checked at runtime! The compiler already proved it's true based on the function's signature.
In essence, the runtime check is equivalent to is List<*>
, but the compiler allows the more specific is List<Int>
syntax because it has already verified the element type. It's not magic; it's a "smart cast" based on a combination of compile-time analysis and a simplified runtime check.
The Ultimate Solution: reified
Types
What if you need to check a generic type without a compile-time guarantee? For that, Kotlin provides its most powerful tool: reified type parameters.
By marking a function as inline
and a type parameter as reified
, you instruct the compiler to copy the function's code directly to the call site, substituting the generic type parameter with the actual type. This preserves the type information for the runtime check.
Here’s how you could write a truly generic type-checking function:
inline fun <reified T> printCollectionType(c: Collection<*>) {
if (c is Collection<T>) {
println("This is a collection of ${T::class.simpleName}: $c")
} else {
println("This is NOT a collection of ${T::class.simpleName}.")
}
}
// Usage:
val myInts = listOf(1, 2, 3)
val myStrings = listOf("a", "b")
printCollectionType<Int>(myInts) // Prints: This is a collection of Int: [1, 2, 3]
printCollectionType<String>(myInts) // Prints: This is NOT a collection of String.
printCollectionType<String>(myStrings) // Prints: This is a collection of String: [a, b]
Conclusion
While type erasure is a fundamental constraint of the JVM, Kotlin provides elegant and safe ways to work with it.
Smart Casts: The compiler uses compile-time information to enable safe, specific-looking runtime checks.
Reified Types: For true runtime generic type inspection, inline functions with reified parameters are the definitive tool. By understanding these mechanisms, you can write code that is not only powerful but also clear, type-safe, and robust. Happy coding
Subscribe to my newsletter
Read articles from Aravinth Velusamy directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
