Kotlin Variables and Type System In-Depth
Kotlin's type system is not Java's clone — it eliminates forced casts with smart casts, builds a complete type algebra with Any/Unit/Nothing, and makes the type system work for you instead of against you.
1. What You'll Learn
- Basic types:
Int/Double/Boolean/Char/String - Smart Cast: use directly after
ischeck - Explicit conversions:
toInt()/toDouble(), no implicit narrowing Any/Unit/Nothing— three top-level types- Type aliases
typealiasfor improved readability
2. A Developer's Real Story
(1) Pain Point: Java's Type-Casting Hell
Bob was maintaining Java code for OrderProcessor, and forced casts were everywhere: (Order), (String). Missing a single instanceof check triggered a ClassCastException — 3-5 production incidents per quarter.
(2) Kotlin's Smart Cast Solution
// Java: verbose and error-prone
if (obj instanceof Order) {
Order order = (Order) obj; // Must cast again
System.out.println(order.getId());
}
// Kotlin: smart cast eliminates the cast
if (obj is Order) {
println(obj.id) // No cast needed!
}
The Kotlin compiler auto-inserts safe casts after
ischecks. Bob never wrote a manual cast again.
3. Basic Types
(1) Numeric Types
Kotlin doesn't expose Java's primitive types (int/double) — everything is an object — but the compiler optimizes them to primitives on the JVM.
val intValue: Int = 42 // 32-bit
val longValue: Long = 42L // 64-bit
val doubleValue: Double = 3.14 // 64-bit IEEE 754
val floatValue: Float = 3.14f // 32-bit IEEE 754
// Underscore for readability
val million = 1_000_000
val creditCard = 1234_5678_9012_3456L
val bytes = 0b11010010_01101001
| Type | Bits | Range | Java Equivalent |
|---|---|---|---|
Byte |
8 | -128 ~ 127 | byte |
Short |
16 | -32768 ~ 32767 | short |
Int |
32 | -2³¹ ~ 2³¹-1 | int |
Long |
64 | -2⁶³ ~ 2⁶³-1 | long |
Float |
32 | IEEE 754 | float |
Double |
64 | IEEE 754 | double |
(2) Non-Numeric Types
val bool: Boolean = true
val char: Char = 'A'
val str: String = "OrderProcessor"
// Character is NOT a number in Kotlin
// val code: Int = 'A' // ERROR
val code: Int = 'A'.code // OK: 65
val charFromCode: Char = 65.toChar() // OK: 'A'
4. Smart Cast
The Kotlin compiler automatically casts a variable to a more specific type after a type check — no manual casting required.
(1) Basic Smart Cast
fun process(input: Any) {
// After 'is' check, compiler smart casts
if (input is String) {
println(input.length) // Smart cast to String
}
// After '!is' check
if (input !is String) return
println(input.length) // Smart cast to String
}
// Smart cast with when
fun describe(obj: Any): String = when (obj) {
is String -> "String: ${obj.length} chars" // Smart cast
is Int -> "Int: ${obj.dec()}" // Smart cast
is List<*> -> "List: ${obj.size} items" // Smart cast
else -> "Unknown"
}
(2) Smart Cast Limitations
// WARNING: smart cast may not work with var or custom getters
var obj: Any = "Hello"
if (obj is String) {
// obj could be changed between check and use
// println(obj.length) // WARNING in some cases
}
// Safe approach: use local val
val safeObj = obj
if (safeObj is String) {
println(safeObj.length) // OK - guaranteed stable
}
(3) Java Cast vs Kotlin Smart Cast
| Dimension | Java | Kotlin |
|---|---|---|
| Syntax | (Type)obj |
Auto |
| Safety | May cause CCE at runtime | Compile-time guarantee |
| Code size | Check + cast (two steps) | Check = cast (one step) |
| Null safety | May cause NPE | Auto handles nullables |
5. Explicit Type Conversions
Kotlin does not support implicit narrowing conversions (e.g., Long → Int) — all conversions must be explicitly called.
(1) Conversion Functions
val longVal: Long = 42L
val intVal: Int = longVal.toInt() // Explicit narrowing
val doubleVal: Double = 3.99
val intFromDouble: Int = doubleVal.toInt() // Truncates: 3
// String to number
val parsed: Int = "42".toInt()
val parsedDouble: Double = "3.14".toDouble()
// Number to string
val strVal: String = 42.toString()
(2) Why No Implicit Conversions?
// This would silently lose data - Kotlin prevents it
val longValue: Long = 2_147_483_648L // Exceeds Int.MAX_VALUE
// val intValue: Int = longValue // COMPILE ERROR
// Must be explicit about potential data loss
val intValue: Int = longValue.toInt() // OK but may overflow
| Conversion Type | Java | Kotlin |
|---|---|---|
| Widening (Int→Long) | Implicit | Explicit toLong() |
| Narrowing (Long→Int) | Implicit (risky) | Explicit toInt() |
| String→Number | Integer.parseInt() |
"42".toInt() |
| Number→String | "" + 42 |
42.toString() |
6. Three Top-Level Types
(1) Type Hierarchy Diagram
classDiagram
Any --> String
Any --> Int
Any --> Order
Any --> Unit
Any --> Nothing
class Any {
+equals()
+hashCode()
+toString()
}
class Unit {
<<singleton>>
}
class Nothing {
<<never returns>>
}
(2) Any: The Parent of All Non-Null Types
// Any is the root of the Kotlin type hierarchy (like Object in Java)
val obj: Any = "Hello"
val obj2: Any = 42
val obj3: Any = Order("ORD-001", 299.99, "CONFIRMED")
// Any has only 3 methods: equals, hashCode, toString
// Use Any? for nullable root type
(3) Unit: The Typed "No Return Value"
// Unit is like void in Java, but it's a real type
fun logOrder(order: Order): Unit { // Unit return is optional
println("Processing ${order.id}")
}
fun logOrder2(order: Order) { // Equivalent - Unit inferred
println("Processing ${order.id}")
}
// Unit is a singleton - can be used as a value
val unitValue: Unit = Unit
(4) Nothing: Never Reaches
// Nothing means "this code never returns"
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// Useful for Elvis operator with exceptions
val customer = order.customer ?: fail("Customer required")
// Infinite loop also returns Nothing
fun infiniteLoop(): Nothing {
while (true) { /* never returns */ }
}
(5) Three Top-Level Types Comparison
| Type | Meaning | Use Case | Java Equivalent |
|---|---|---|---|
Any |
Parent of all non-null types | Generic references | Object |
Unit |
No meaningful return value | Side-effect functions | void |
Nothing |
Never reaches the end | Throw exceptions / infinite loops | None |
7. Type Aliases (typealias)
// Simplify complex type signatures
typealias OrderMap = Map<String, List<Order>>
typealias OrderProcessor = (List<Order>) -> List<String>
typealias USD = Double
// Usage
val ordersByCustomer: OrderMap = mapOf(
"Alice" to listOf(Order("ORD-001", 299.99, "CONFIRMED"))
)
typealias Predicate<T> = (T) -> Boolean
val isHighValue: Predicate<Order> = { it.total > 10_000 }
// typealias does NOT create new types - just aliases
val map: OrderMap = ordersByCustomer // Same type
8. Complete Example: OrderProcessor Type-Safe Processing
// ============================================
// OrderProcessor - Type-Safe Event Processing
// Feature: Process order events with smart cast
// ============================================
typealias OrderId = String
typealias USD = Double
sealed class OrderEvent {
data class Created(val orderId: OrderId, val total: USD) : OrderEvent()
data class Paid(val orderId: OrderId, val amount: USD) : OrderEvent()
data class Shipped(val orderId: OrderId, val trackingCode: String) : OrderEvent()
data class Cancelled(val orderId: OrderId, val reason: String) : OrderEvent()
}
fun handleEvent(event: OrderEvent): String = when (event) {
is OrderEvent.Created -> {
// Smart cast: event.orderId and event.total available
val threshold = 10_000
val priority = if (event.total > threshold) "VIP" else "STANDARD"
"Order ${event.orderId} created (\$${event.total} USD) -> $priority"
}
is OrderEvent.Paid -> {
val tax = event.amount * 0.08
"Order ${event.orderId} paid: \$${event.amount} USD (tax: \$$tax USD)"
}
is OrderEvent.Shipped ->
"Order ${event.orderId} shipped: ${event.trackingCode}"
is OrderEvent.Cancelled ->
"Order ${event.orderId} cancelled: ${event.reason}"
}
fun main() {
val events: List<OrderEvent> = listOf(
OrderEvent.Created("ORD-001", 299.99),
OrderEvent.Created("ORD-002", 15_000.00),
OrderEvent.Paid("ORD-001", 299.99),
OrderEvent.Shipped("ORD-001", "TRK-ABC123"),
OrderEvent.Cancelled("ORD-003", "Customer request")
)
events.forEach { event ->
val result = handleEvent(event)
println(result)
}
// Type check with smart cast
val anyEvent: Any = events[0]
if (anyEvent is OrderEvent.Created) {
println("\nSmart cast works: ${anyEvent.orderId} costs \$${anyEvent.total} USD")
}
}
Output:
Order ORD-001 created ($299.99 USD) -> STANDARD
Order ORD-002 created ($15000.0 USD) -> VIP
Order ORD-001 paid: $299.99 USD (tax: $23.999200000000002 USD)
Order ORD-001 shipped: TRK-ABC123
Order ORD-003 cancelled: Customer request
Smart cast works: ORD-001 costs $299.99 USD
❓ FAQ
Int and Java's int the same thing?Int compiles to Java's int (primitive type), and auto-boxes to Integer when an object is needed. Developers don't need to worry about this difference.var and may be modified concurrently, or has a custom getter, the compiler cannot guarantee consistent types between check and use — smart cast won't apply.Nothing type?typealias create a new type?typealias is just a type alias — the compiler replaces it with the original type. For true type-safe wrappers, use inline class / value class.Long→Int) silently lose data and are a common source of bugs. Explicit conversions make developers aware of potential data loss.Any and Any??Any is the parent of all non-null types; Any? is the parent of all types (including nullable). Any? = Any | null is the true top of Kotlin's type system.📖 Summary
- Kotlin basic types are objects in code but the compiler optimizes them to primitives on the JVM
- Smart cast auto-casts after
ischecks, eliminating Java's forced type conversions - All type conversions must be explicit (
toInt()/toDouble()), no implicit narrowing Anyis the root of non-null types,Unitis the typed version of void,Nothingmeans never returnstypealiasprovides readable aliases for complex types without creating new types- Numeric underscores
1_000_000improve readability for large numbers
📝 Exercises
- Beginner (⭐): Declare variables of different types (
Int,Double,Boolean,String) and print their types::class. Hint: useval x = 42; println(x::class) - Intermediate (⭐⭐): Write a function that takes
Any, using smart cast to handleString(print length) andInt(print squared value). Hint:is String/is Int - Challenge (⭐⭐⭐): Use
typealiasand sealed classes to design an order event type system, withwhenexhaustively handling all event types. Hint: see Section 8's complete example



