Kotlin Type Inference

Let Kotlin automatically determine variable types

🤖 What is Kotlin Type Inference?

Type inference automatically determines variable types from assigned values. You don't need to explicitly declare types - Kotlin figures them out, making code cleaner while maintaining type safety.


// Kotlin infers types automatically
val name = "Alice"          // Inferred as String
val age = 25               // Inferred as Int
val price = 19.99          // Inferred as Double
val isActive = true        // Inferred as Boolean
val items = listOf(1, 2, 3) // Inferred as List<Int>
                                    

Output:

name: Alice (String)

age: 25 (Int)

price: 19.99 (Double)

isActive: true (Boolean)

items: [1, 2, 3] (List<Int>)

Type Inference Benefits

Cleaner Code

Less verbose declarations

val name = "John"
🛡️

Type Safety

Still maintains strong typing

val count = 42 // Int
🚀

Faster Development

Less typing, more productivity

val list = mutableListOf()
🔍

Smart Detection

Infers complex types automatically

val map = mapOf("a" to 1)

🔹 Basic Type Inference

Kotlin automatically infers types from literal values:

// Number types
val smallInt = 42                    // Int
val bigInt = 3000000000L            // Long (L suffix)
val decimal = 3.14                  // Double
val float = 3.14f                   // Float (f suffix)
val byte: Byte = 127                // Explicit type needed for Byte
val short: Short = 32000            // Explicit type needed for Short

// Text types
val singleChar = 'A'                // Char
val text = "Hello"                  // String
val multiLine = """
    Multi-line
    string
""".trimIndent()                    // String

// Boolean type
val isTrue = true                   // Boolean
val isFalse = false                 // Boolean

// Comparison results
val isGreater = 10 > 5              // Boolean (from comparison)
val isEqual = "a" == "b"            // Boolean (from comparison)

Output:

smallInt: 42 (Int)

bigInt: 3000000000 (Long)

decimal: 3.14 (Double)

text: Hello (String)

isGreater: true (Boolean)

🔹 Collection Type Inference

Kotlin infers collection types from their contents:

// List types
val numbers = listOf(1, 2, 3, 4, 5)           // List<Int>
val names = listOf("Alice", "Bob", "Charlie")  // List<String>
val mixed = listOf(1, "two", 3.0)             // List<Any>
val emptyList = emptyList<String>()            // List<String> (explicit type needed)

// Mutable lists
val mutableNumbers = mutableListOf(1, 2, 3)   // MutableList<Int>
val mutableNames = mutableListOf<String>()     // MutableList<String> (explicit for empty)

// Set types
val uniqueNumbers = setOf(1, 2, 3, 2, 1)      // Set<Int> (duplicates removed)
val uniqueNames = setOf("Alice", "Bob")        // Set<String>

// Map types
val ages = mapOf("Alice" to 25, "Bob" to 30)   // Map<String, Int>
val scores = mapOf(1 to "A", 2 to "B")         // Map<Int, String>
val emptyMap = emptyMap<String, Int>()         // Map<String, Int> (explicit type needed)

// Array types
val intArray = arrayOf(1, 2, 3, 4)            // Array<Int>
val stringArray = arrayOf("a", "b", "c")      // Array<String>

Output:

numbers: [1, 2, 3, 4, 5] (List<Int>)

names: [Alice, Bob, Charlie] (List<String>)

mixed: [1, two, 3.0] (List<Any>)

ages: {Alice=25, Bob=30} (Map<String, Int>)

uniqueNumbers: [1, 2, 3] (Set<Int>)

🔹 Function Return Type Inference

Kotlin can infer return types for functions:

// Simple function with inferred return type
fun add(a: Int, b: Int) = a + b                    // Returns Int

fun greet(name: String) = "Hello, $name!"          // Returns String

fun isEven(number: Int) = number % 2 == 0           // Returns Boolean

fun getMax(a: Int, b: Int) = if (a > b) a else b   // Returns Int

// Functions with block body need explicit return type (if not Unit)
fun multiply(a: Int, b: Int): Int {
    return a * b
}

// Unit return type is inferred for functions that don't return a value
fun printMessage(msg: String) {                     // Returns Unit (inferred)
    println(msg)
}

// Lambda expressions with inferred types
val square = { x: Int -> x * x }                    // (Int) -> Int
val isPositive = { num: Int -> num > 0 }            // (Int) -> Boolean
val concatenate = { a: String, b: String -> a + b } // (String, String) -> String

// Higher-order functions
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }                // List<Int> inferred
val evenNumbers = numbers.filter { it % 2 == 0 }   // List<Int> inferred
val sum = numbers.reduce { acc, n -> acc + n }      // Int inferred

Output:

add(5, 3): 8 (Int)

greet("Alice"): Hello, Alice! (String)

isEven(4): true (Boolean)

doubled: [2, 4, 6, 8, 10] (List<Int>)

sum: 15 (Int)

🔹 When to Use Explicit Types

Sometimes you need to specify types explicitly:

// When inference might be ambiguous
val number: Long = 42                    // Explicit Long instead of inferred Int
val precise: Float = 3.14f               // Explicit Float instead of inferred Double

// For empty collections
val emptyStringList: List<String> = emptyList()
val emptyIntSet: Set<Int> = emptySet()
val emptyMap: Map<String, Int> = emptyMap()

// When you want a more general type
val items: List<Any> = listOf(1, "two", 3.0)  // Explicit Any instead of inferred types

// For nullable types when initial value is null
val name: String? = null                 // Must specify nullable type
val age: Int? = null                     // Cannot infer from null alone

// When working with interfaces or abstract classes
interface Shape {
    fun area(): Double
}

class Circle(val radius: Double) : Shape {
    override fun area() = 3.14159 * radius * radius
}

val shape: Shape = Circle(5.0)           // Explicit interface type

// For function parameters (always required)
fun processData(data: List<String>, count: Int): String {
    return "Processed ${data.size} items, count: $count"
}

// When type inference would be too complex
val complexMap: Map<String, List<Pair<Int, String>>> = mapOf(
    "group1" to listOf(1 to "one", 2 to "two"),
    "group2" to listOf(3 to "three", 4 to "four")
)

Output:

number: 42 (Long - explicit)

emptyStringList: [] (List<String> - explicit)

name: null (String? - explicit nullable)

shape: Circle instance (Shape interface)

🔹 Type Inference Best Practices

Guidelines for effective use of type inference:

Best Practices:

  • Let Kotlin infer when obvious: val name = "John" instead of val name: String = "John"
  • Be explicit when unclear: Specify types for better readability
  • Use explicit types for public APIs: Makes interfaces clearer
  • Specify nullable types: When starting with null values
  • Consider team preferences: Some teams prefer explicit types
// Good: Clear and concise
val userName = "alice123"
val userAge = 25
val isActive = true
val scores = listOf(95, 87, 92)

// Good: Explicit when needed
val userId: Long = 12345L                    // Specific numeric type
val config: Map<String, Any> = loadConfig()  // Complex return type
val callback: ((String) -> Unit)? = null     // Nullable function type

// Avoid: Unnecessary explicit types
val message: String = "Hello"                // String is obvious
val count: Int = 42                          // Int is obvious
val flag: Boolean = true                     // Boolean is obvious

// Good: Public API with explicit types
class UserService {
    fun findUser(id: Long): User? {          // Clear parameter and return types
        // Implementation
        return null
    }
    
    fun getActiveUsers(): List<User> {        // Clear return type
        // Implementation
        return emptyList()
    }
}

// Good: Balance of inference and explicitness
data class Product(
    val id: Long,                            // Explicit for clarity
    val name: String,                        // Explicit for clarity
    val price: Double                        // Explicit for clarity
) {
    val displayPrice = "$%.2f".format(price) // Inferred String
    val isExpensive = price > 100.0          // Inferred Boolean
}

🧠 Test Your Knowledge

What type does Kotlin infer for: val result = 10 / 3?