iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

val / var

Kotlin variables: val vs var, type inference, nullability, lateinit, lazy, const, and the rules that make idiomatic Kotlin readable.

Kotlin — variables

EXAMPLE
// ===== val (immutable binding) and var (mutable) =====
fun demo() {
    val name = "Alex"        // immutable reference
    // name = "Sam"          // error: val cannot be reassigned

    var count = 0
    count += 1                 // ok
    count = 10

    // Default to val. Reach for var only when the binding actually changes.

    // ===== Explicit types =====
    val age: Int = 30
    val pi: Double = 3.14
    val on: Boolean = true
    val ch: Char = 'A'
    val s: String = "hi"
    val ids: List<Int> = listOf(1, 2, 3)

    // ===== Nullability =====
    val maybe: String? = null  // explicitly nullable
    val length = maybe?.length ?: 0   // safe call + Elvis

    // !! force-unwrap (use sparingly):
    val forced = maybe!!.length       // throws NPE if null

    // ===== Smart casts =====
    val raw: Any = 42
    if (raw is Int) {
        // raw is smart-cast to Int inside this block
        println(raw + 1)
    }

    // ===== lateinit (var, non-null, deferred init) =====
    // Useful for DI / test setup where the value is known to be set before first use.
    // lateinit applies to var of non-null reference type (not primitives or nullable).

    // ===== by lazy (val, computed on first read, thread-safe) =====
    val expensive: String by lazy { computeOnce() }

    // ===== const val (compile-time constant on object/companion) =====
    // (See top-level/companion below.)
}

fun computeOnce(): String = "hello"

// ===== Top-level / companion constants =====
const val APP_NAME = "shop"

object Config {
    const val MAX_RETRIES = 3
}

class Money(val cents: Long, val currency: String = "AUD") {
    companion object {
        const val DECIMAL_PLACES = 2
        val ZERO = Money(0)
    }
}

// ===== Destructuring =====
data class Point(val x: Int, val y: Int)
val (x, y) = Point(3, 4)

// ===== Multiple assignment from a map =====
val (k, v) = "a" to 1     // Pair

// ===== let / also / apply / run / with: scope functions =====
val emailLength = "alex@example.com".let { it.length }   // 16

// ===== Read-only collections vs MutableXxx =====
val list = listOf(1, 2, 3)        // List<Int> — read-only
val mList = mutableListOf(1, 2)   // MutableList<Int>

// You can reassign val on read-only collection? No, val locks the binding.
// You CAN mutate a MutableList via val (mList.add(3) works); the binding is locked, not the object.

// ===== When to use var =====
// - Loop counters (although ranges + map handle most cases)
// - Truly mutable UI state
// - Hot-path code where allocation matters
// Otherwise: val, val, val.

// ===== Patterns to internalise =====
// - Default to val + immutable collections
// - Use type inference; annotate when the right-hand side is opaque
// - String? where null is meaningful; non-null otherwise
// - by lazy for expensive, single-init values
// - data class for value-shaped types; equals/hashCode/copy come free

// ===== Pitfalls =====
// - !! everywhere -> nullability lost; convert to ? + ?: or let
// - lateinit on a primitive or nullable type -> compile error
// - const val on a non-primitive / non-companion -> compile error
// - Mutating an object through a val binding and assuming the binding is 'frozen'
// - lazy without a thread-safety mode in concurrent code (default is LazyThreadSafetyMode.SYNCHRONIZED, which is correct but slow)

Why it matters

val first, var only when you must, nullability explicit, by lazy for one-shot expensive values. Once these are reflex, Kotlin code reads close to a domain model: data classes for shapes, immutable collections by default, scope functions for chained transforms.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
val pi = 3.14159  // read-only
var count = 0     // mutable
count += 1
Try it Yourself »

Exercise

Read-only binding.

pi = 3.14

Test yourself

Q1. A read-only variable is declared with…
Q2. A mutable variable is declared with…
Q3. A compile-time constant uses…

Discussion

Loading…