Chapter 3 of 12 ~15 min read

Variables & Types

The val/var model, type inference, Kotlin's type system, and type casting.


val vs var — Immutability First

Kotlin introduces a strict immutability model. Instead of overloading final everywhere, you choose val (immutable) or var (mutable) upfront:

Java
// Immutable requires 'final'
final String name = "Alice";

// Mutable (default)
String city = "KL";
city = "Penang"; // allowed

// Final can't be reassigned
// name = "Bob"; // ❌ compile error
Kotlin
// val = immutable (like Java final)
val name = "Alice"
// name = "Bob" // ❌ compile error

// var = mutable
var city = "KL"
city = "Penang" // ✅ allowed

// Convention: prefer val over var
Best Practice

Always start with val and only switch to var when you actually need reassignment. This makes code more predictable and thread-safe.

Type Inference

Kotlin infers types from the right-hand side at compile time — this is not dynamic typing. The type is fixed, just inferred:

Java (explicit types)
int count = 42;
double pi = 3.14;
String text = "hello";
boolean isActive = true;
List<String> items = new ArrayList<>();
Kotlin (inferred — still statically typed)
val count = 42        // Int
val pi = 3.14         // Double
val text = "hello"    // String
val isActive = true   // Boolean
val items = mutableListOf<String>()

// Explicit type when needed:
val count2: Int = 42

Kotlin's Basic Types

Kotlin does not have Java's primitive/object duality. All types are objects — the compiler optimizes to JVM primitives where possible:

Kotlin TypeJava EquivalentNotes
Intint / Integer32-bit signed
Longlong / Long64-bit signed, append L: 100L
Doubledouble / Double64-bit float (default for decimals)
Floatfloat / FloatAppend f: 3.14f
Booleanboolean / Booleantrue / false
Charchar / CharacterSingle character: 'A'
StringStringImmutable, supports templates
AnyObjectRoot of the type hierarchy
UnitvoidReturn type for functions with no value
NothingNo direct equivalentFunction that never returns (throws/loops)

Type Casting — as and is

Kotlin's type check and cast operators replace Java's instanceof and explicit casts:

Java
Object obj = "Hello";

if (obj instanceof String) {
    // Must cast manually
    String s = (String) obj;
    System.out.println(s.length());
}

// Unsafe cast: ClassCastException risk
String s2 = (String) obj;
Kotlin
val obj: Any = "Hello"

// 'is' check — like instanceof
if (obj is String) {
    // Smart cast: obj IS String here!
    println(obj.length) // no explicit cast
}

// Safe cast: returns null if fails
val s: String? = obj as? String

// Unsafe cast (like Java)
val s2 = obj as String // ClassCastException if fails
Smart Casts

After an is check, Kotlin automatically smart-casts the variable to the checked type inside the if block. No redundant explicit cast needed — unlike Java's instanceof pattern.

Multi-line Strings

Kotlin — raw multi-line string with """
val json = """
    {
        "name": "Alice",
        "age": 30
    }
""".trimIndent()
// No escape characters needed inside triple quotes