ZetCode

Kotlin is Keyword

last modified April 19, 2025

Kotlin's type checking system provides powerful ways to verify object types at runtime. The is keyword is central to type checking operations. This tutorial explores the is keyword in depth with practical examples.

Basic Definitions

The is keyword in Kotlin checks whether an object is of a specific type. It returns true if the object matches the type, false otherwise. When used with smart casts, it enables automatic type conversion within scope.

Basic Type Checking

The simplest use of is checks if an object is of a certain type. This is useful when working with generic types like Any.

BasicCheck.kt
package com.zetcode

fun main() {

    val obj: Any = "Hello Kotlin"
    
    if (obj is String) {
        println("It's a String")
    } else {
        println("It's not a String")
    }
}

Here we check if obj is a String. The is operator returns true, so the first branch executes. This is the most straightforward use of type checking in Kotlin.

Smart Casting

Kotlin's smart casting automatically casts objects after type checks. Within the scope where is returns true, the object is treated as that type.

SmartCast.kt
package com.zetcode

fun printLength(obj: Any) {
    if (obj is String) {
        println(obj.length) // Smart cast to String
    }
}

fun main() {

    printLength("Kotlin") // Output: 6
    printLength(123) // No output
}

After checking obj is String, Kotlin automatically treats obj as a String inside the if block. We can directly call String methods like length without explicit casting.

Negative Check with !is

The !is operator is the negation of is. It checks if an object is not of a certain type. Smart casting works with negative checks too.

NegativeCheck.kt
package com.zetcode

fun processValue(value: Any) {
    if (value !is String) {
        println("Not a string: $value")
    } else {
        println(value.uppercase())
    }
}

fun main() {

    processValue(42) // Output: Not a string: 42
    processValue("hello") // Output: HELLO
}

The !is operator checks if value is not a String. In the else branch, value is smart cast to String, allowing us to call uppercase().

When Expressions with is

The is keyword works well with when expressions. This allows for clean pattern matching against multiple possible types.

WhenCheck.kt
package com.zetcode

fun describe(obj: Any): String = when (obj) {
    is String -> "String with length ${obj.length}"
    is Int -> "Integer with value $obj"
    is Double -> "Double with value $obj"
    else -> "Unknown type"
}

fun main() {

    println(describe("Kotlin")) // Output: String with length 6
    println(describe(42)) // Output: Integer with value 42
    println(describe(3.14)) // Output: Double with value 3.14
}

The when expression checks the type of obj using is. Each branch automatically smart casts to the checked type, allowing type-specific operations. This is a clean alternative to if-else chains.

Checking Generic Types

With reified type parameters, you can check generic types at runtime. This extends is functionality to generic type checking.

GenericCheck.kt
package com.zetcode

inline fun <reified T> checkType(obj: Any) {
    if (obj is T) {
        println("Object is of type ${T::class.simpleName}")
    } else {
        println("Object is not of type ${T::class.simpleName}")
    }
}

fun main() {

    checkType<String>("Hello") // Output: Object is of type String
    checkType<Int>("World") // Output: Object is not of type Int
}

The reified type parameter preserves type information at runtime. This allows is to check against generic type T. Without reified, this check wouldn't be possible due to type erasure.

Checking Sealed Classes

The is keyword is particularly useful with sealed classes. It allows exhaustive checking of all possible subtypes in a type-safe way.

SealedCheck.kt
package com.zetcode

sealed class Result
class Success(val data: String) : Result()
class Error(val message: String) : Result()

fun handleResult(result: Result) {
    when (result) {
        is Success -> println("Success: ${result.data}")
        is Error -> println("Error: ${result.message}")
    }
}

fun main() {

    handleResult(Success("Data loaded")) // Output: Success: Data loaded
    handleResult(Error("Network error")) // Output: Error: Network error
}

The sealed class ensures all possible subtypes are known. The when expression with is checks provides exhaustive handling. Each branch smart casts to the specific subtype, allowing access to subtype properties.

Checking Nullable Types

The is operator handles nullable types gracefully. It can check both the type and null status of an object in one operation.

NullableCheck.kt
package com.zetcode

fun checkNullable(value: Any?) {
    if (value is String?) {
        println("It's a nullable String")
    }
    
    if (value is String) {
        println("It's a non-null String: $value")
    } else if (value == null) {
        println("It's null")
    }
}

fun main() {

    checkNullable("Hello") // Output: It's a nullable String + non-null String
    checkNullable(null) // Output: It's a nullable String + It's null
}

The first check with String? matches both String and null. The second check with String only matches non-null Strings. This shows how is can distinguish between nullable and non-null types.

Best Practices for Type Checking

Source

Kotlin Type Checks and Casts Documentation

This tutorial covered Kotlin's is keyword in depth, showing its use for type checking and enabling smart casts. We explored various scenarios including sealed classes, generics, and nullable types. Proper use of type checking can make your code more robust and type-safe.

Author

My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.

List all Kotlin tutorials.