Kotlin Syntax & Variables

val vs var, null safety with ?, ?: and !!, the when expression, and string templates.

val vs var

Kotlin has two ways to declare a variable, and choosing between them is a decision you make constantly:

Kotlin
val name = "Ada"     // read-only reference — cannot be reassigned
var age = 30          // mutable reference — can be reassigned

age = 31               // fine
// name = "Grace"     // compile error: val cannot be reassigned

val doesn't necessarily mean the underlying object is immutable — it means the reference can't be pointed at something else. A val list can still have its contents changed if it's a mutable list type:

Kotlin
val numbers = mutableListOf(1, 2, 3)
numbers.add(4)          // fine — mutating the list's CONTENTS, not reassigning 'numbers' itself
// numbers = mutableListOf(5, 6) // compile error — this WOULD be reassignment

println(numbers) // [1, 2, 3, 4]

Default to val. Prefer it everywhere a variable doesn't genuinely need to change after initialization — it communicates intent clearly and rules out an entire class of accidental-reassignment bugs. Reach for var only when reassignment is truly needed (a loop counter, an accumulator).

Type inference

Kotlin is statically typed, but the compiler infers types from context so you rarely have to spell them out:

Kotlin
val count = 10           // inferred as Int
val price = 19.99         // inferred as Double
val name: String = "Ada"  // explicit type — occasionally clearer, always legal

Null safety

This is Kotlin's headline feature. A regular type (String) can never hold null — the compiler rejects it at compile time, not at runtime:

Kotlin
var name: String = "Ada"
// name = null // compile error: Null can not be a value of a non-null type String

To allow null, you explicitly mark the type as nullable with ?:

Kotlin
var nickname: String? = "Ace" // nullable — CAN legally hold null
nickname = null                 // fine now

// println(nickname.length) // compile error — must handle the null case first

Once a type is nullable, Kotlin forces you to deal with the possibility of null before you can use it — you can't just call a method on it and hope for the best, the way you could in Java before it added its own optional annotations.

Safe call (?.) — evaluates to null instead of throwing, if the receiver is null:

Kotlin
val nickname: String? = null
println(nickname?.length) // null — safely short-circuits instead of crashing

Elvis operator (?:) — supplies a default value when the left side is null:

Kotlin
val nickname: String? = null
val displayName = nickname ?: "Anonymous" // "Anonymous" — used because nickname was null
println(displayName)

Not-null assertion (!!) — forces a nullable value to be treated as non-null, throwing NullPointerException immediately if it actually was null:

Kotlin
val nickname: String? = null
// println(nickname!!.length) // throws NullPointerException immediately — use sparingly!

!! exists mainly as an escape hatch for interop with Java code (which has no concept of nullability at the type level) or for cases where you can prove nullness is impossible but the compiler can't. Reaching for it routinely defeats the entire purpose of Kotlin's null-safety system — prefer ?., ?:, or an explicit if (x != null) check instead.

Kotlin
fun describeLength(text: String?): String {
    return if (text != null) {
        "Length is ${text.length}" // smart-cast: Kotlin knows text can't be null inside this branch
    } else {
        "No text provided"
    }
}

when — a more powerful switch

when is Kotlin's replacement for switch, and — like Kotlin's if — it's an expression: it evaluates to a value.

Kotlin
fun describe(code: Int): String = when (code) {
    200, 201 -> "Success"           // multiple values sharing one branch
    404 -> "Not Found"
    in 500..599 -> "Server Error"    // range check
    else -> "Unknown"
}

println(describe(404)) // Not Found
println(describe(503)) // Server Error

when can also be used with no argument at all, acting as a cleaner chain of conditions than a long if/else if ladder:

Kotlin
fun categorize(age: Int): String = when {
    age < 13 -> "child"
    age < 20 -> "teenager"
    age < 65 -> "adult"
    else -> "senior"
}

String templates

Kotlin interpolates variables and expressions directly into strings with $:

Kotlin
val name = "Ada"
val age = 30

println("Hello, $name! You are $age years old.")        // Hello, Ada! You are 30 years old.
println("Next year you'll be ${age + 1}.")                // curly braces needed for expressions

Common mistakes

  • Defaulting to var out of habit instead of val — most variables never actually need to be reassigned.
  • Overusing !! to silence a compiler complaint instead of handling the null case properly — this reintroduces exactly the NullPointerException risk Kotlin's type system exists to prevent.
  • Forgetting when used as an expression must be exhaustive (every possible input must produce a value, usually via an else branch) — the compiler enforces this, but it's easy to be surprised by the error the first time.

Interview questions

Q: What's the difference between val and var? val declares a read-only reference that can only be assigned once — like Java's final. var declares a mutable reference that can be reassigned. val doesn't make the underlying object immutable by itself; a val referring to a mutable collection can still have its contents changed, just not be pointed at a different collection.

Q: How does Kotlin prevent NullPointerException at compile time? By distinguishing nullable types (String?) from non-nullable types (String) in the type system itself — a non-nullable type can never legally hold null, so the compiler rejects any code path that could assign or pass null into one. To work with a value that might be null, you use a nullable type and the compiler then forces you to handle that possibility (via ?., ?:, an explicit null check, or the escape-hatch !!) before you can use it.