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:
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:
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:
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:
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 ?:
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:
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:
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:
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.
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.
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:
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 $:
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
varout of habit instead ofval— most variables never actually need to be reassigned. - Overusing
!!to silence a compiler complaint instead of handling thenullcase properly — this reintroduces exactly theNullPointerExceptionrisk Kotlin's type system exists to prevent. - Forgetting
whenused as an expression must be exhaustive (every possible input must produce a value, usually via anelsebranch) — 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.