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
}